namespace NATS.Server.Configuration;

/// <summary>
/// Remote leaf node entry parsed from the remotes[] array inside a leafnodes {} block.
/// Go reference: opts.go RemoteLeafOpts struct.
/// </summary>
public sealed class RemoteLeafOptions
{
    /// <summary>Local account to bind this remote to.</summary>
    public string? LocalAccount { get; init; }

    /// <summary>Path to credentials file.</summary>
    public string? Credentials { get; init; }

    /// <summary>URLs for this remote entry.</summary>
    public List<string> Urls { get; init; } = [];

    /// <summary>Whether to not randomize URL order.</summary>
    public bool DontRandomize { get; init; }

    private int _urlIndex = -1;
    private TimeSpan _connectDelay;
    private Timer? _migrateTimer;

    /// <summary>Last URL selected by <see cref="PickNextUrl"/>.</summary>
    public string? CurrentUrl { get; private set; }

    /// <summary>Saved TLS hostname for SNI usage on solicited connections.</summary>
    public string? TlsName { get; private set; }

    /// <summary>Username parsed from URL user-info fallback.</summary>
    public string? Username { get; private set; }

    /// <summary>Password parsed from URL user-info fallback.</summary>
    public string? Password { get; private set; }

    /// <summary>
    /// Returns the next URL using round-robin order and updates <see cref="CurrentUrl"/>.
    /// Go reference: leafnode.go leafNodeCfg.pickNextURL.
    /// </summary>
    public string PickNextUrl()
    {
        if (Urls.Count == 0)
            throw new InvalidOperationException("No remote leaf URLs configured.");

        var idx = Interlocked.Increment(ref _urlIndex);
        var next = Urls[idx % Urls.Count];
        CurrentUrl = next;
        return next;
    }

    /// <summary>
    /// Returns the current selected URL, or null if no URL has been selected yet.
    /// Go reference: leafnode.go leafNodeCfg.getCurrentURL.
    /// </summary>
    public string? GetCurrentUrl() => CurrentUrl;

    /// <summary>
    /// Returns the currently configured reconnect/connect delay for this remote.
    /// Go reference: leafnode.go leafNodeCfg.getConnectDelay.
    /// </summary>
    public TimeSpan GetConnectDelay() => _connectDelay;

    /// <summary>
    /// Sets reconnect/connect delay for this remote.
    /// Go reference: leafnode.go leafNodeCfg.setConnectDelay.
    /// </summary>
    public void SetConnectDelay(TimeSpan delay) => _connectDelay = delay;

    /// <summary>
    /// Starts or replaces the JetStream migration timer callback for this remote leaf.
    /// Go reference: leafnode.go leafNodeCfg.migrateTimer.
    /// </summary>
    public void StartMigrateTimer(TimerCallback callback, TimeSpan delay)
    {
        ArgumentNullException.ThrowIfNull(callback);
        var timer = new Timer(callback, null, delay, Timeout.InfiniteTimeSpan);
        var previous = Interlocked.Exchange(ref _migrateTimer, timer);
        previous?.Dispose();
    }

    /// <summary>
    /// Cancels the JetStream migration timer if active.
    /// Go reference: leafnode.go leafNodeCfg.cancelMigrateTimer.
    /// </summary>
    public void CancelMigrateTimer()
    {
        var timer = Interlocked.Exchange(ref _migrateTimer, null);
        timer?.Dispose();
    }

    /// <summary>
    /// Saves TLS hostname from URL for future SNI usage.
    /// Go reference: leafnode.go leafNodeCfg.saveTLSHostname.
    /// </summary>
    public void SaveTlsHostname(string url)
    {
        if (TryParseUrl(url, out var uri))
            TlsName = uri.Host;
    }

    /// <summary>
    /// Saves username/password from URL user info for fallback auth.
    /// Go reference: leafnode.go leafNodeCfg.saveUserPassword.
    /// </summary>
    public void SaveUserPassword(string url)
    {
        if (!TryParseUrl(url, out var uri) || string.IsNullOrEmpty(uri.UserInfo))
            return;

        var parts = uri.UserInfo.Split(':', 2, StringSplitOptions.None);
        Username = Uri.UnescapeDataString(parts[0]);
        Password = parts.Length > 1 ? Uri.UnescapeDataString(parts[1]) : string.Empty;
    }

    private static bool TryParseUrl(string url, out Uri uri)
    {
        if (Uri.TryCreate(url, UriKind.Absolute, out uri!))
            return true;

        return Uri.TryCreate($"nats://{url}", UriKind.Absolute, out uri!);
    }
}

public sealed class LeafNodeOptions
{
    public string Host { get; set; } = "0.0.0.0";
    public int Port { get; set; }

    // Auth for leaf listener
    public string? Username { get; set; }
    public string? Password { get; set; }
    public double AuthTimeout { get; set; }

    // Advertise address
    public string? Advertise { get; set; }

    // Per-subsystem write deadline
    public TimeSpan WriteDeadline { get; set; }

    /// <summary>
    /// Simple URL list for programmatic setup (tests, server-code wiring).
    /// Parsed config populates RemoteLeaves instead.
    /// </summary>
    public List<string> Remotes { get; set; } = [];

    /// <summary>
    /// Remote leaf node entries parsed from a config file (remotes: [] array).
    /// Each entry has a local account, credentials, and a list of URLs.
    /// </summary>
    public List<RemoteLeafOptions> RemoteLeaves { get; set; } = [];

    /// <summary>
    /// JetStream domain for this leaf node.
    /// Go reference: leafnode.go -- JsDomain in leafNodeCfg.
    /// </summary>
    public string? JetStreamDomain { get; set; }

    public List<string> DenyExports { get; set; } = [];
    public List<string> DenyImports { get; set; } = [];
    public List<string> ExportSubjects { get; set; } = [];
    public List<string> ImportSubjects { get; set; } = [];

    /// <summary>List of users for leaf listener authentication (from authorization.users).</summary>
    public List<NATS.Server.Auth.User>? Users { get; set; }
}
