using System;
using System.Security.Principal;
using System.Threading;
using Serilog;
using ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Backend;
using ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Ipc;

namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware;

/// <summary>
///     Entry point for the Wonderware Historian sidecar. Reads pipe name, allowed-SID,
///     shared secret, and historian connection config from environment (the supervisor
///     passes them at spawn time per <c>driver-stability.md</c>). Hosts a named-pipe server
///     dispatching the five sidecar contracts (PR 3.3) to the Wonderware Historian SDK.
/// </summary>
public static class Program
{
    /// <summary>Entry point for the Wonderware Historian sidecar process.</summary>
    /// <param name="args">Command-line arguments (unused).</param>
    /// <returns>0 on success, 2 on fatal error.</returns>
    public static int Main(string[] args)
    {
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Information()
            .WriteTo.File(
                @"%ProgramData%\OtOpcUa\historian-wonderware-.log".Replace("%ProgramData%", Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)),
                rollingInterval: RollingInterval.Day)
            .CreateLogger();

        try
        {
            var pipeName = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_PIPE")
                ?? throw new InvalidOperationException("OTOPCUA_HISTORIAN_PIPE not set — supervisor must pass the sidecar pipe name");
            var allowedSidValue = Environment.GetEnvironmentVariable("OTOPCUA_ALLOWED_SID")
                ?? throw new InvalidOperationException("OTOPCUA_ALLOWED_SID not set — supervisor must pass the server principal SID");
            var sharedSecret = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_SECRET")
                ?? throw new InvalidOperationException("OTOPCUA_HISTORIAN_SECRET not set — supervisor must pass the per-process secret at spawn time");

            var allowedSid = new SecurityIdentifier(allowedSidValue);

            using var cts = new CancellationTokenSource();
            Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); };

            // Sidecar can boot in "pipe-only" mode (no real Wonderware Historian SDK
            // initialization) for smoke + IPC tests. Production sets ENABLED=true so the
            // SDK opens its connection up front.
            var historianEnabled = string.Equals(
                Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_ENABLED"),
                "true", StringComparison.OrdinalIgnoreCase);

            if (!historianEnabled)
            {
                Log.Information("Wonderware historian sidecar starting in pipe-only mode (OTOPCUA_HISTORIAN_ENABLED!=true) — pipe={Pipe} allowedSid={Sid}", pipeName, allowedSidValue);
                cts.Token.WaitHandle.WaitOne();
                Log.Information("Wonderware historian sidecar stopping cleanly");
                return 0;
            }

            using var historian = BuildHistorian();
            var alarmWriter = BuildAlarmWriter();
            var handler = new HistorianFrameHandler(historian, Log.Logger, alarmWriter);
            using var server = new PipeServer(pipeName, allowedSid, sharedSecret, Log.Logger);

            Log.Information("Wonderware historian sidecar serving — pipe={Pipe} allowedSid={Sid}", pipeName, allowedSidValue);
            try { server.RunAsync(handler, cts.Token).GetAwaiter().GetResult(); }
            catch (OperationCanceledException) { /* clean shutdown via Ctrl-C */ }

            Log.Information("Wonderware historian sidecar stopped cleanly");
            return 0;
        }
        catch (Exception ex)
        {
            Log.Fatal(ex, "Wonderware historian sidecar fatal");
            return 2;
        }
        finally { Log.CloseAndFlush(); }
    }

    /// <summary>
    ///     Builds the Wonderware Historian data source from environment variables. Mirrors
    ///     the env-var contract that <c>Driver.Galaxy.Host</c> used in v1; PR 3.W reaffirms
    ///     this contract in install scripts.
    /// </summary>
    private static HistorianDataSource BuildHistorian()
    {
        var cfg = new HistorianConfiguration
        {
            Enabled = true,
            ServerName = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_SERVER") ?? "localhost",
            Port = TryParseInt("OTOPCUA_HISTORIAN_PORT", 32568),
            IntegratedSecurity = !string.Equals(Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_INTEGRATED"), "false", StringComparison.OrdinalIgnoreCase),
            UserName = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_USER"),
            Password = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_PASS"),
            CommandTimeoutSeconds = TryParseInt("OTOPCUA_HISTORIAN_TIMEOUT_SEC", 30),
            MaxValuesPerRead = TryParseInt("OTOPCUA_HISTORIAN_MAX_VALUES", 10000),
            FailureCooldownSeconds = TryParseInt("OTOPCUA_HISTORIAN_COOLDOWN_SEC", 60),
        };

        var servers = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_SERVERS");
        if (!string.IsNullOrWhiteSpace(servers))
            cfg.ServerNames = new System.Collections.Generic.List<string>(
                servers.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries));

        Log.Information("Sidecar Historian config — {NodeCount} node(s), port={Port}",
            cfg.ServerNames.Count > 0 ? cfg.ServerNames.Count : 1, cfg.Port);
        return new HistorianDataSource(cfg);
    }

    private static int TryParseInt(string envName, int defaultValue)
    {
        var raw = Environment.GetEnvironmentVariable(envName);
        return int.TryParse(raw, out var parsed) ? parsed : defaultValue;
    }

    /// <summary>
    ///     Constructs the alarm-event writer when the alarm-write toggle is on, otherwise
    ///     returns <c>null</c> so <see cref="HistorianFrameHandler"/> falls back to the
    ///     "not configured" reply for any incoming <c>WriteAlarmEvents</c> frame.
    ///     Default is <c>true</c> when <c>OTOPCUA_HISTORIAN_ENABLED=true</c>; explicitly
    ///     set <c>OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED=false</c> to keep a read-only
    ///     deployment that still loads the SDK for reads.
    /// </summary>
    internal static IAlarmEventWriter? BuildAlarmWriter()
    {
        var raw = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED");
        var enabled = string.IsNullOrWhiteSpace(raw)
            ? true
            : !string.Equals(raw, "false", StringComparison.OrdinalIgnoreCase);

        if (!enabled)
        {
            Log.Information("Alarm-event writer disabled (OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED=false); historian sidecar will reject WriteAlarmEvents frames.");
            return null;
        }

        var cfg = BuildAlarmWriterConfig();
        var backend = new SdkAlarmHistorianWriteBackend(cfg);
        Log.Information("Alarm-event writer enabled — backend=SdkAlarmHistorianWriteBackend server={Server}", cfg.ServerName);
        return new AahClientManagedAlarmEventWriter(backend);
    }

    private static HistorianConfiguration BuildAlarmWriterConfig()
    {
        return new HistorianConfiguration
        {
            Enabled = true,
            ServerName = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_SERVER") ?? "localhost",
            Port = TryParseInt("OTOPCUA_HISTORIAN_PORT", 32568),
            IntegratedSecurity = !string.Equals(Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_INTEGRATED"), "false", StringComparison.OrdinalIgnoreCase),
            UserName = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_USER"),
            Password = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_PASS"),
            CommandTimeoutSeconds = TryParseInt("OTOPCUA_HISTORIAN_TIMEOUT_SEC", 30),
            FailureCooldownSeconds = TryParseInt("OTOPCUA_HISTORIAN_COOLDOWN_SEC", 60),
        };
    }
}
