// Copyright 2012-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Adapted from parse utility functions in server/opts.go in the NATS server Go source.

using System.Net.Security;
using System.Security.Authentication;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

namespace ZB.MOM.NatsNet.Server.Config;

/// <summary>
/// Converts NATS duration strings (e.g. "2s", "100ms", "1h30m") to <see cref="TimeSpan"/>.
/// Mirrors Go <c>parseDuration</c> in server/opts.go.
/// </summary>
public sealed class NatsDurationJsonConverter : JsonConverter<TimeSpan>
{
    private static readonly Regex Pattern = new(
        @"^(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?(?:(\d+)ms)?(?:(\d+)us)?(?:(\d+)ns)?$",
        RegexOptions.Compiled | RegexOptions.IgnoreCase);

    public override TimeSpan Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var raw = reader.GetString() ?? throw new JsonException("Expected a duration string");
        return Parse(raw);
    }

    public override void Write(Utf8JsonWriter writer, TimeSpan value, JsonSerializerOptions options)
        => writer.WriteStringValue(FormatDuration(value));

    /// <summary>
    /// Parses a NATS-style duration string. Accepts Go time.Duration format strings and ISO 8601.
    /// </summary>
    public static TimeSpan Parse(string s)
    {
        if (string.IsNullOrWhiteSpace(s))
            throw new FormatException("Duration string is empty");

        // Try Go-style: e.g. "2s", "100ms", "1h30m", "5m10s"
        var m = Pattern.Match(s);
        if (m.Success && m.Value.Length > 0)
        {
            var hours   = m.Groups[1].Success ? int.Parse(m.Groups[1].Value) : 0;
            var minutes = m.Groups[2].Success ? int.Parse(m.Groups[2].Value) : 0;
            var seconds = m.Groups[3].Success ? int.Parse(m.Groups[3].Value) : 0;
            var ms      = m.Groups[4].Success ? int.Parse(m.Groups[4].Value) : 0;
            var us      = m.Groups[5].Success ? int.Parse(m.Groups[5].Value) : 0;
            var ns      = m.Groups[6].Success ? int.Parse(m.Groups[6].Value) : 0;
            return new TimeSpan(0, hours, minutes, seconds, ms)
                + TimeSpan.FromMicroseconds(us)
                + TimeSpan.FromTicks(ns / 100); // 1 tick = 100 ns
        }

        // Try .NET TimeSpan.Parse (handles "hh:mm:ss")
        if (TimeSpan.TryParse(s, out var ts)) return ts;

        throw new FormatException($"Cannot parse duration string: \"{s}\"");
    }

    private static string FormatDuration(TimeSpan ts)
    {
        if (ts.TotalMilliseconds < 1) return $"{(long)ts.TotalNanoseconds}ns";
        if (ts.TotalSeconds < 1) return $"{(long)ts.TotalMilliseconds}ms";
        if (ts.TotalMinutes < 1) return $"{(long)ts.TotalSeconds}s";
        if (ts.TotalHours < 1)   return $"{ts.Minutes}m{ts.Seconds}s";
        return $"{(int)ts.TotalHours}h{ts.Minutes}m{ts.Seconds}s";
    }
}

/// <summary>
/// Converts a TLS version string ("1.2", "1.3", "TLS12") to <see cref="SslProtocols"/>.
/// Mirrors Go <c>parseTLSVersion</c> in server/opts.go.
/// </summary>
public sealed class TlsVersionJsonConverter : JsonConverter<SslProtocols>
{
    public override SslProtocols Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var raw = reader.GetString()?.Trim() ?? string.Empty;
        return Parse(raw);
    }

    public override void Write(Utf8JsonWriter writer, SslProtocols value, JsonSerializerOptions options)
        => writer.WriteStringValue(value.ToString());

    public static SslProtocols Parse(string s) => s.ToUpperInvariant() switch
    {
        "1.2" or "TLS12" or "TLSV1.2" => SslProtocols.Tls12,
        "1.3" or "TLS13" or "TLSV1.3" => SslProtocols.Tls13,
        _ => throw new FormatException($"Unknown TLS version: \"{s}\""),
    };
}

/// <summary>
/// Validates and normalises a NATS URL string (nats://host:port).
/// Mirrors Go <c>parseURL</c> in server/opts.go.
/// </summary>
public sealed class NatsUrlJsonConverter : JsonConverter<string>
{
    public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        var raw = reader.GetString() ?? string.Empty;
        return Normalise(raw);
    }

    public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options)
        => writer.WriteStringValue(value);

    public static string Normalise(string url)
    {
        if (string.IsNullOrWhiteSpace(url)) return url;
        url = url.Trim();
        if (!url.Contains("://")) url = "nats://" + url;
        if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
            throw new FormatException($"Invalid NATS URL: \"{url}\"");
        return uri.ToString().TrimEnd('/');
    }
}

/// <summary>
/// Converts a storage size string ("1GB", "512MB", "1024") to a byte count (long).
/// Mirrors Go <c>getStorageSize</c> in server/opts.go.
/// </summary>
public sealed class StorageSizeJsonConverter : JsonConverter<long>
{
    private static readonly Regex Pattern = new(@"^(\d+(?:\.\d+)?)\s*([KMGT]?B?)?$",
        RegexOptions.Compiled | RegexOptions.IgnoreCase);

    public override long Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType == JsonTokenType.Number)
        {
            return reader.GetInt64();
        }
        var raw = reader.GetString() ?? "0";
        return Parse(raw);
    }

    public override void Write(Utf8JsonWriter writer, long value, JsonSerializerOptions options)
        => writer.WriteNumberValue(value);

    public static long Parse(string s)
    {
        // Mirrors Go getStorageSize: empty string returns 0 with no error.
        if (string.IsNullOrWhiteSpace(s)) return 0;
        if (long.TryParse(s, out var n)) return n;
        var m = Pattern.Match(s.Trim());
        if (!m.Success) throw new FormatException($"Invalid storage size: \"{s}\"");
        var num = double.Parse(m.Groups[1].Value);
        var suffix = m.Groups[2].Value.ToUpperInvariant();
        return suffix switch
        {
            "K" or "KB" => (long)(num * 1024),
            "M" or "MB" => (long)(num * 1024 * 1024),
            "G" or "GB" => (long)(num * 1024 * 1024 * 1024),
            "T" or "TB" => (long)(num * 1024L * 1024 * 1024 * 1024),
            _            => (long)num,
        };
    }
}
