using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using NATS.Server.Configuration;
using NATS.Server.JetStream.Api;

namespace NATS.Server.JetStream;

// Maps to Go's enableJetStream() in server/jetstream.go:414-523.
// Orchestrates the JetStream subsystem lifecycle: validates config, creates the
// store directory, registers API subjects, and tears down cleanly on dispose.
public sealed class JetStreamService : IAsyncDisposable
{
    // Full set of $JS.API.> subjects registered at startup.
    // Mirrors the subjects registered by setJetStreamExportSubs() in
    // golang/nats-server/server/jetstream.go and jsApiSubs in jetstream_api.go.
    private static readonly IReadOnlyList<string> AllApiSubjects =
    [
        "$JS.API.>",
        JetStreamApiSubjects.Info,
        JetStreamApiSubjects.StreamCreate + "*",
        JetStreamApiSubjects.StreamUpdate + "*",
        JetStreamApiSubjects.StreamDelete + "*",
        JetStreamApiSubjects.StreamInfo + "*",
        JetStreamApiSubjects.StreamNames,
        JetStreamApiSubjects.StreamList,
        JetStreamApiSubjects.StreamPurge + "*",
        JetStreamApiSubjects.StreamMessageGet + "*",
        JetStreamApiSubjects.StreamMessageDelete + "*",
        JetStreamApiSubjects.StreamSnapshot + "*",
        JetStreamApiSubjects.StreamRestore + "*",
        JetStreamApiSubjects.StreamLeaderStepdown + "*",
        JetStreamApiSubjects.ConsumerCreate + "*",
        JetStreamApiSubjects.ConsumerDelete + "*.*",
        JetStreamApiSubjects.ConsumerInfo + "*.*",
        JetStreamApiSubjects.ConsumerNames + "*",
        JetStreamApiSubjects.ConsumerList + "*",
        JetStreamApiSubjects.ConsumerPause + "*.*",
        JetStreamApiSubjects.ConsumerNext + "*.*",
        JetStreamApiSubjects.DirectGet + "*",
        JetStreamApiSubjects.MetaLeaderStepdown,
    ];

    private readonly JetStreamOptions _options;
    private readonly ILogger<JetStreamService> _logger;
    private List<string> _registeredApiSubjects = [];

    public InternalClient? InternalClient { get; }
    public bool IsRunning { get; private set; }

    /// <summary>
    /// The API subjects registered with the server after a successful StartAsync.
    /// Empty before start or after dispose.
    /// </summary>
    public IReadOnlyList<string> RegisteredApiSubjects => _registeredApiSubjects;

    /// <summary>
    /// Maximum streams limit from configuration. 0 means unlimited.
    /// Maps to Go's JetStreamAccountLimits.MaxStreams.
    /// </summary>
    public int MaxStreams => _options.MaxStreams;

    /// <summary>
    /// Maximum consumers limit from configuration. 0 means unlimited.
    /// Maps to Go's JetStreamAccountLimits.MaxConsumers.
    /// </summary>
    public int MaxConsumers => _options.MaxConsumers;

    /// <summary>
    /// Maximum memory store bytes from configuration. 0 means unlimited.
    /// Maps to Go's JetStreamConfig.MaxMemory.
    /// </summary>
    public long MaxMemory => _options.MaxMemoryStore;

    /// <summary>
    /// Maximum file store bytes from configuration. 0 means unlimited.
    /// Maps to Go's JetStreamConfig.MaxStore.
    /// </summary>
    public long MaxStore => _options.MaxFileStore;

    public JetStreamService(JetStreamOptions options, InternalClient? internalClient = null)
        : this(options, internalClient, NullLoggerFactory.Instance)
    {
    }

    public JetStreamService(JetStreamOptions options, InternalClient? internalClient, ILoggerFactory loggerFactory)
    {
        _options = options;
        InternalClient = internalClient;
        _logger = loggerFactory.CreateLogger<JetStreamService>();
    }

    // Maps to Go's enableJetStream() in server/jetstream.go:414-523.
    // Validates the store directory, creates it if absent, then registers all
    // $JS.API.> subjects so inbound API messages can be routed.
    public Task StartAsync(CancellationToken ct)
    {
        if (IsRunning)
        {
            _logger.LogDebug("JetStream is already running; ignoring duplicate StartAsync");
            return Task.CompletedTask;
        }

        // Validate and create store directory when specified.
        // Go: os.MkdirAll(cfg.StoreDir, defaultDirPerms) — jetstream.go:430-444.
        if (!string.IsNullOrEmpty(_options.StoreDir))
        {
            if (Directory.Exists(_options.StoreDir))
            {
                _logger.LogDebug("JetStream store directory already exists: {StoreDir}", _options.StoreDir);
            }
            else
            {
                Directory.CreateDirectory(_options.StoreDir);
                _logger.LogInformation("JetStream store directory created: {StoreDir}", _options.StoreDir);
            }
        }
        else
        {
            _logger.LogInformation("JetStream running in memory-only mode (no StoreDir configured)");
        }

        // Register all $JS.API.> subjects.
        // Go: setJetStreamExportSubs() — jetstream.go:489-494.
        _registeredApiSubjects = [.. AllApiSubjects];

        IsRunning = true;

        _logger.LogInformation(
            "JetStream started. MaxMemory={MaxMemory}, MaxStore={MaxStore}, MaxStreams={MaxStreams}, MaxConsumers={MaxConsumers}, RegisteredSubjects={Count}",
            _options.MaxMemoryStore,
            _options.MaxFileStore,
            _options.MaxStreams,
            _options.MaxConsumers,
            _registeredApiSubjects.Count);

        return Task.CompletedTask;
    }

    // Maps to Go's shutdown path in jetstream.go.
    // Clears registered subjects and marks the service as not running.
    public ValueTask DisposeAsync()
    {
        _registeredApiSubjects = [];
        IsRunning = false;
        _logger.LogInformation("JetStream stopped");
        return ValueTask.CompletedTask;
    }
}
