using NATS.Server;
using NATS.Server.Configuration;
using Serilog;
using Serilog.Sinks.SystemConsole.Themes;

// First pass: scan args for -c flag to get config file path
string? configFile = null;
for (int i = 0; i < args.Length; i++)
{
    if (args[i] == "-c" && i + 1 < args.Length)
    {
        configFile = args[++i];
        break;
    }
}

var windowsService = false;

// If config file specified, load it as the base options
var options = configFile != null
    ? ConfigProcessor.ProcessConfigFile(configFile)
    : new NatsOptions();

// Second pass: apply CLI args on top of config-loaded options, tracking InCmdLine
for (int i = 0; i < args.Length; i++)
{
    switch (args[i])
    {
        case "-p" or "--port" when i + 1 < args.Length:
            options.Port = int.Parse(args[++i]);
            options.InCmdLine.Add("Port");
            break;
        case "-a" or "--addr" when i + 1 < args.Length:
            options.Host = args[++i];
            options.InCmdLine.Add("Host");
            break;
        case "-n" or "--name" when i + 1 < args.Length:
            options.ServerName = args[++i];
            options.InCmdLine.Add("ServerName");
            break;
        case "-m" or "--http_port" when i + 1 < args.Length:
            options.MonitorPort = int.Parse(args[++i]);
            options.InCmdLine.Add("MonitorPort");
            break;
        case "--http_base_path" when i + 1 < args.Length:
            options.MonitorBasePath = args[++i];
            options.InCmdLine.Add("MonitorBasePath");
            break;
        case "--https_port" when i + 1 < args.Length:
            options.MonitorHttpsPort = int.Parse(args[++i]);
            options.InCmdLine.Add("MonitorHttpsPort");
            break;
        case "-c" when i + 1 < args.Length:
            // Already handled in first pass; skip the value
            i++;
            break;
        case "--pid" when i + 1 < args.Length:
            options.PidFile = args[++i];
            options.InCmdLine.Add("PidFile");
            break;
        case "--ports_file_dir" when i + 1 < args.Length:
            options.PortsFileDir = args[++i];
            options.InCmdLine.Add("PortsFileDir");
            break;
        case "--tls":
            break;
        case "--tlscert" when i + 1 < args.Length:
            options.TlsCert = args[++i];
            options.InCmdLine.Add("TlsCert");
            break;
        case "--tlskey" when i + 1 < args.Length:
            options.TlsKey = args[++i];
            options.InCmdLine.Add("TlsKey");
            break;
        case "--tlscacert" when i + 1 < args.Length:
            options.TlsCaCert = args[++i];
            options.InCmdLine.Add("TlsCaCert");
            break;
        case "--tlsverify":
            options.TlsVerify = true;
            options.InCmdLine.Add("TlsVerify");
            break;
        case "-D" or "--debug":
            options.Debug = true;
            options.InCmdLine.Add("Debug");
            break;
        case "-V" or "-T" or "--trace":
            options.Trace = true;
            options.InCmdLine.Add("Trace");
            break;
        case "-DV":
            options.Debug = true;
            options.Trace = true;
            options.InCmdLine.Add("Debug");
            options.InCmdLine.Add("Trace");
            break;
        case "-l" or "--log" or "--log_file" when i + 1 < args.Length:
            options.LogFile = args[++i];
            options.InCmdLine.Add("LogFile");
            break;
        case "--log_size_limit" when i + 1 < args.Length:
            options.LogSizeLimit = long.Parse(args[++i]);
            options.InCmdLine.Add("LogSizeLimit");
            break;
        case "--log_max_files" when i + 1 < args.Length:
            options.LogMaxFiles = int.Parse(args[++i]);
            options.InCmdLine.Add("LogMaxFiles");
            break;
        case "--logtime" when i + 1 < args.Length:
            options.Logtime = bool.Parse(args[++i]);
            options.InCmdLine.Add("Logtime");
            break;
        case "--logtime_utc":
            options.LogtimeUTC = true;
            options.InCmdLine.Add("LogtimeUTC");
            break;
        case "--syslog":
            options.Syslog = true;
            options.InCmdLine.Add("Syslog");
            break;
        case "--remote_syslog" when i + 1 < args.Length:
            options.RemoteSyslog = args[++i];
            options.InCmdLine.Add("RemoteSyslog");
            break;
        case "--service":
            windowsService = true;
            break;
        case "--log_level_override" when i + 1 < args.Length:
            var parts = args[++i].Split('=', 2);
            if (parts.Length == 2)
            {
                options.LogOverrides ??= new();
                options.LogOverrides[parts[0]] = parts[1];
            }
            break;
    }
}

// Build Serilog configuration from options
var logConfig = new LoggerConfiguration()
    .Enrich.FromLogContext();

// Set minimum level based on flags
if (options.Trace)
    logConfig.MinimumLevel.Verbose();
else if (options.Debug)
    logConfig.MinimumLevel.Debug();
else
    logConfig.MinimumLevel.Information();

// Build output template
var timestampFormat = options.LogtimeUTC
    ? "{Timestamp:yyyy/MM/dd HH:mm:ss.ffffff} "
    : "{Timestamp:HH:mm:ss} ";
var template = options.Logtime
    ? $"[{timestampFormat}{{Level:u3}}] {{Message:lj}}{{NewLine}}{{Exception}}"
    : "[{Level:u3}] {Message:lj}{NewLine}{Exception}";

// Console sink with color auto-detection
if (!Console.IsOutputRedirected)
    logConfig.WriteTo.Console(outputTemplate: template, theme: AnsiConsoleTheme.Code);
else
    logConfig.WriteTo.Console(outputTemplate: template);

// File sink with rotation
if (!string.IsNullOrEmpty(options.LogFile))
{
    logConfig.WriteTo.File(
        options.LogFile,
        fileSizeLimitBytes: options.LogSizeLimit > 0 ? options.LogSizeLimit : null,
        retainedFileCountLimit: options.LogMaxFiles > 0 ? options.LogMaxFiles : null,
        rollOnFileSizeLimit: options.LogSizeLimit > 0,
        outputTemplate: template);
}

// Syslog sink
if (!string.IsNullOrEmpty(options.RemoteSyslog))
{
    logConfig.WriteTo.UdpSyslog(options.RemoteSyslog);
}
else if (options.Syslog)
{
    logConfig.WriteTo.LocalSyslog("nats-server");
}

// Apply per-subsystem log level overrides
if (options.LogOverrides is not null)
{
    foreach (var (ns, level) in options.LogOverrides)
    {
        if (Enum.TryParse<Serilog.Events.LogEventLevel>(level, true, out var serilogLevel))
            logConfig.MinimumLevel.Override(ns, serilogLevel);
    }
}

Log.Logger = logConfig.CreateLogger();

if (windowsService)
{
    Log.Information("Windows Service mode requested");
}

using var loggerFactory = new Serilog.Extensions.Logging.SerilogLoggerFactory(Log.Logger);
using var server = new NatsServer(options, loggerFactory);

// Store CLI snapshot for reload precedence (CLI flags always win over config file)
if (configFile != null && options.InCmdLine.Count > 0)
{
    var cliSnapshot = new NatsOptions();
    ConfigReloader.MergeCliOverrides(cliSnapshot, options, options.InCmdLine);
    server.SetCliSnapshot(cliSnapshot, options.InCmdLine);
}

// Register signal handlers
server.HandleSignals();

server.ReOpenLogFile = () =>
{
    Log.Information("Reopening log file");
    Log.CloseAndFlush();
    Log.Logger = logConfig.CreateLogger();
    Log.Information("File log re-opened");
};

// Ctrl+C triggers graceful shutdown
Console.CancelKeyPress += (_, e) =>
{
    e.Cancel = true;
    Log.Information("Trapped SIGINT signal");
    _ = Task.Run(async () => await server.ShutdownAsync());
};

try
{
    _ = server.StartAsync(CancellationToken.None);
    await server.WaitForReadyAsync();
    server.WaitForShutdown();
}
catch (OperationCanceledException)
{
    Log.Information("Server shutdown requested");
}
finally
{
    Log.CloseAndFlush();
}
