using System;
using System.Security.Principal;
using System.Threading;
using Serilog;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend.Galaxy;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend.Historian;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Backend.MxAccess;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Ipc;
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Sta;

namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host;

/// <summary>
///     Entry point for the <c>OtOpcUaGalaxyHost</c> Windows service / console host. Reads the
///     pipe name, allowed-SID, and shared secret from environment (passed by the supervisor at
///     spawn time per <c>driver-stability.md</c>).
/// </summary>
public static class Program
{
    public static int Main(string[] args)
    {
        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Information()
            .WriteTo.File(
                @"%ProgramData%\OtOpcUa\galaxy-host-.log".Replace("%ProgramData%", Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)),
                rollingInterval: RollingInterval.Day)
            .CreateLogger();

        try
        {
            var pipeName = Environment.GetEnvironmentVariable("OTOPCUA_GALAXY_PIPE") ?? "OtOpcUaGalaxy";
            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_GALAXY_SECRET")
                ?? throw new InvalidOperationException("OTOPCUA_GALAXY_SECRET not set — supervisor must pass the per-process secret at spawn time");

            var allowedSid = new SecurityIdentifier(allowedSidValue);

            using var server = new PipeServer(pipeName, allowedSid, sharedSecret, Log.Logger);
            using var cts = new CancellationTokenSource();
            Console.CancelKeyPress += (_, e) => { e.Cancel = true; cts.Cancel(); };

            Log.Information("OtOpcUaGalaxyHost starting — pipe={Pipe} allowedSid={Sid}", pipeName, allowedSidValue);

            // Backend selection — env var picks the implementation:
            //   OTOPCUA_GALAXY_BACKEND=stub       → StubGalaxyBackend (no Galaxy required)
            //   OTOPCUA_GALAXY_BACKEND=db         → DbBackedGalaxyBackend (Discover only, against ZB)
            //   OTOPCUA_GALAXY_BACKEND=mxaccess   → MxAccessGalaxyBackend (real COM + ZB; default)
            var backendKind = Environment.GetEnvironmentVariable("OTOPCUA_GALAXY_BACKEND")?.ToLowerInvariant() ?? "mxaccess";
            var zbConn = Environment.GetEnvironmentVariable("OTOPCUA_GALAXY_ZB_CONN")
                         ?? "Server=localhost;Database=ZB;Integrated Security=True;TrustServerCertificate=True;Encrypt=False;";
            var clientName = Environment.GetEnvironmentVariable("OTOPCUA_GALAXY_CLIENT_NAME") ?? "OtOpcUa-Galaxy.Host";

            IGalaxyBackend backend;
            StaPump? pump = null;
            MxAccessClient? mx = null;
            switch (backendKind)
            {
                case "stub":
                    backend = new StubGalaxyBackend();
                    break;
                case "db":
                    backend = new DbBackedGalaxyBackend(new GalaxyRepository(new GalaxyRepositoryOptions { ConnectionString = zbConn }));
                    break;
                default: // mxaccess
                    pump = new StaPump("Galaxy.Sta");
                    pump.WaitForStartedAsync().GetAwaiter().GetResult();
                    mx = new MxAccessClient(pump, new MxProxyAdapter(), clientName);
                    var historian = BuildHistorianIfEnabled();
                    backend = new MxAccessGalaxyBackend(
                        new GalaxyRepository(new GalaxyRepositoryOptions { ConnectionString = zbConn }),
                        mx,
                        historian);
                    break;
            }

            Log.Information("OtOpcUaGalaxyHost backend={Backend}", backendKind);
            var handler = new GalaxyFrameHandler(backend, Log.Logger);
            try { server.RunAsync(handler, cts.Token).GetAwaiter().GetResult(); }
            finally
            {
                (backend as IDisposable)?.Dispose();
                mx?.Dispose();
                pump?.Dispose();
            }

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

    /// <summary>
    ///     Builds a <see cref="HistorianDataSource"/> from the OTOPCUA_HISTORIAN_* environment
    ///     variables the supervisor passes at spawn time. Returns null when the historian is
    ///     disabled (default) so <c>MxAccessGalaxyBackend.HistoryReadAsync</c> returns a clear
    ///     "not configured" error instead of attempting an SDK connection to localhost.
    /// </summary>
    private static IHistorianDataSource? BuildHistorianIfEnabled()
    {
        var enabled = Environment.GetEnvironmentVariable("OTOPCUA_HISTORIAN_ENABLED");
        if (!string.Equals(enabled, "true", StringComparison.OrdinalIgnoreCase) && enabled != "1")
            return null;

        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("Historian enabled — {NodeCount} configured 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;
    }
}
