namespace NATS.Server.Configuration;

public sealed class GatewayOptions
{
    public string? Name { get; set; }
    public string Host { get; set; } = "0.0.0.0";
    public int Port { get; set; }
    public List<string> Remotes { get; set; } = [];

    // Go: opts.go — gateway authorization fields
    public bool RejectUnknown { get; set; }
    public string? Username { get; set; }
    public string? Password { get; set; }
    public double AuthTimeout { get; set; }
    public string? Advertise { get; set; }
    public int ConnectRetries { get; set; }
    public bool ConnectBackoff { get; set; }
    public TimeSpan WriteDeadline { get; set; }

    // Go: opts.go — gateways remotes list (RemoteGatewayOpts)
    public List<RemoteGatewayOptions> RemoteGateways { get; set; } = [];
}

/// <summary>
/// Go: opts.go RemoteGatewayOpts struct — a single remote gateway entry.
/// </summary>
public sealed class RemoteGatewayOptions
{
    private int _connAttempts;

    public string? Name { get; set; }
    public List<string> Urls { get; set; } = [];
    public bool Implicit { get; set; }
    public byte[]? Hash { get; set; }
    public byte[]? OldHash { get; set; }
    public string? TlsName { get; private set; }
    public bool VarzUpdateUrls { get; set; }

    /// <summary>
    /// Deep clone helper for remote gateway options.
    /// Go reference: RemoteGatewayOpts.clone.
    /// </summary>
    public RemoteGatewayOptions Clone()
    {
        return new RemoteGatewayOptions
        {
            Name = Name,
            Urls = [.. Urls],
            Implicit = Implicit,
            Hash = Hash == null ? null : [.. Hash],
            OldHash = OldHash == null ? null : [.. OldHash],
            TlsName = TlsName,
            VarzUpdateUrls = VarzUpdateUrls,
        };
    }

    public int BumpConnAttempts() => Interlocked.Increment(ref _connAttempts);

    public int GetConnAttempts() => Volatile.Read(ref _connAttempts);

    public void ResetConnAttempts() => Interlocked.Exchange(ref _connAttempts, 0);

    public bool IsImplicit() => Implicit;

    public List<Uri> GetUrls(Random? random = null)
    {
        var urls = new List<Uri>();
        foreach (var url in Urls)
        {
            if (TryNormalizeRemoteUrl(url, out var uri))
                urls.Add(uri);
        }

        random ??= Random.Shared;
        for (var i = urls.Count - 1; i > 0; i--)
        {
            var j = random.Next(i + 1);
            (urls[i], urls[j]) = (urls[j], urls[i]);
        }

        return urls;
    }

    public List<string> GetUrlsAsStrings()
    {
        var result = new List<string>();
        foreach (var uri in GetUrls())
            result.Add($"{uri.Scheme}://{uri.Authority}");
        return result;
    }

    public void UpdateUrls(IEnumerable<string> configuredUrls, IEnumerable<string> discoveredUrls)
    {
        var merged = new List<string>();
        AddUrlsInternal(merged, configuredUrls);
        AddUrlsInternal(merged, discoveredUrls);
        Urls = merged;
    }

    public void SaveTlsHostname(string url)
    {
        if (TryNormalizeRemoteUrl(url, out var uri))
            TlsName = uri.Host;
    }

    public void AddUrls(IEnumerable<string> discoveredUrls)
    {
        AddUrlsInternal(Urls, discoveredUrls);
    }

    private static void AddUrlsInternal(List<string> target, IEnumerable<string> urls)
    {
        var seen = new HashSet<string>(target, StringComparer.OrdinalIgnoreCase);
        foreach (var url in urls)
        {
            if (!TryNormalizeRemoteUrl(url, out var uri))
                continue;

            var normalized = $"{uri.Scheme}://{uri.Authority}";
            if (seen.Add(normalized))
                target.Add(normalized);
        }
    }

    private static bool TryNormalizeRemoteUrl(string? raw, out Uri uri)
    {
        uri = default!;
        if (string.IsNullOrWhiteSpace(raw))
            return false;

        var normalized = raw.Contains("://", StringComparison.Ordinal) ? raw : $"nats://{raw}";
        if (Uri.TryCreate(normalized, UriKind.Absolute, out var parsed) && parsed is not null)
        {
            uri = parsed;
            return true;
        }

        return false;
    }
}
