using System;
using System.IO.Pipes;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Serilog;
using ZB.MOM.WW.GRAccess.Cli.GRAccess;
using ZB.MOM.WW.GRAccess.Cli.Protocol;

namespace ZB.MOM.WW.GRAccess.Cli.Session
{
    public sealed class SessionDaemon : IDisposable
    {
        private static readonly ILogger Log = Serilog.Log.ForContext<SessionDaemon>();

        private readonly string _galaxyName;
        private readonly string _nodeName;
        private readonly string _pipeName;
        private readonly TimeSpan _idleTimeout;
        private readonly CancellationTokenSource _shutdownCts = new CancellationTokenSource();

        private StaComThread _staThread;
        private GRAccessConnection _connection;
        private Mutex _instanceMutex;
        private DateTime _lastActivity;
        private Timer _idleTimer;

        public SessionDaemon(string galaxyName, string nodeName, TimeSpan idleTimeout)
        {
            _galaxyName = galaxyName;
            _nodeName = nodeName;
            _pipeName = PipeProtocol.GetPipeName(galaxyName);
            _idleTimeout = idleTimeout;
            _lastActivity = DateTime.UtcNow;
        }

        /// <summary>
        /// Static entry point called from Program.Main when --daemon flag is present.
        /// </summary>
        public static int Run(string[] args)
        {
            string galaxyName = null;
            string nodeName = null;
            int idleTimeoutMinutes = 30;

            for (int i = 1; i < args.Length; i++)
            {
                switch (args[i])
                {
                    case "--galaxy" when i + 1 < args.Length:
                        galaxyName = args[++i];
                        break;
                    case "--node" when i + 1 < args.Length:
                        nodeName = args[++i];
                        break;
                    case "--idle-timeout" when i + 1 < args.Length:
                        int.TryParse(args[++i], out idleTimeoutMinutes);
                        break;
                }
            }

            if (string.IsNullOrEmpty(galaxyName) || string.IsNullOrEmpty(nodeName))
            {
                Console.Error.WriteLine("--galaxy and --node are required for daemon mode.");
                return 1;
            }

            // Configure daemon logging
            Serilog.Log.Logger = new LoggerConfiguration()
                .WriteTo.File(
                    System.IO.Path.Combine(
                        Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
                        "ZB.MOM.WW.GRAccess.Cli", "logs", $"daemon-{galaxyName.ToLowerInvariant()}.log"),
                    rollingInterval: RollingInterval.Day,
                    retainedFileCountLimit: 7)
                .CreateLogger();

            try
            {
                using (var daemon = new SessionDaemon(galaxyName, nodeName,
                    TimeSpan.FromMinutes(idleTimeoutMinutes)))
                {
                    return daemon.Start();
                }
            }
            catch (Exception ex)
            {
                Log.Fatal(ex, "Daemon terminated unexpectedly");
                return 1;
            }
            finally
            {
                Serilog.Log.CloseAndFlush();
            }
        }

        public int Start()
        {
            // 1. Acquire mutex
            var mutexName = $"Global\\graccess-session-{_galaxyName.ToLowerInvariant()}";
            _instanceMutex = new Mutex(true, mutexName, out bool createdNew);
            if (!createdNew)
            {
                Log.Warning("Another daemon is already running for galaxy {Galaxy}", _galaxyName);
                return 1;
            }

            try
            {
                // 2. Start STA thread
                _staThread = new StaComThread();
                _staThread.Start();

                // 3. Connect to galaxy on STA thread
                _connection = new GRAccessConnection(_galaxyName, _nodeName);
                _staThread.RunAsync(() => _connection.Connect()).GetAwaiter().GetResult();

                // 4. Write session info
                var sessionInfo = new SessionInfo
                {
                    GalaxyName = _galaxyName,
                    NodeName = _nodeName,
                    PipeName = _pipeName,
                    ProcessId = System.Diagnostics.Process.GetCurrentProcess().Id,
                    StartedAtUtc = DateTime.UtcNow
                };
                sessionInfo.Save();

                Log.Information("Daemon started for galaxy {Galaxy} on pipe {Pipe}", _galaxyName, _pipeName);

                // 5. Start idle timer
                _idleTimer = new Timer(_ => CheckIdleTimeout(), null,
                    TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30));

                // 6. Accept connections (blocks until shutdown)
                AcceptConnectionsAsync(_shutdownCts.Token).GetAwaiter().GetResult();

                return 0;
            }
            catch (Exception ex)
            {
                Serilog.Log.Error(ex, "Daemon startup failed for galaxy {Galaxy} on node {Node}", _galaxyName, _nodeName);
                throw;
            }
            finally
            {
                // 7. Cleanup
                _idleTimer?.Dispose();

                if (_connection != null)
                {
                    try
                    {
                        _staThread.RunAsync(() => _connection.Disconnect()).GetAwaiter().GetResult();
                    }
                    catch (Exception ex)
                    {
                        Log.Warning(ex, "Error disconnecting during shutdown");
                    }
                }

                _staThread?.Dispose();

                var info = SessionInfo.Load(_galaxyName);
                info?.Delete();

                _instanceMutex.ReleaseMutex();
                _instanceMutex.Dispose();

                Log.Information("Daemon stopped for galaxy {Galaxy}", _galaxyName);
            }
        }

        private async Task AcceptConnectionsAsync(CancellationToken ct)
        {
            while (!ct.IsCancellationRequested)
            {
                try
                {
                    using (var server = new NamedPipeServerStream(
                        _pipeName,
                        PipeDirection.InOut,
                        NamedPipeServerStream.MaxAllowedServerInstances,
                        PipeTransmissionMode.Byte,
                        PipeOptions.Asynchronous))
                    {
                        var waitTask = Task.Factory.FromAsync(
                            server.BeginWaitForConnection, server.EndWaitForConnection, null);

                        // Wait for connection or cancellation
                        var completedTask = await Task.WhenAny(waitTask, Task.Delay(-1, ct))
                            .ConfigureAwait(false);

                        if (ct.IsCancellationRequested)
                            break;

                        await waitTask.ConfigureAwait(false);

                        _lastActivity = DateTime.UtcNow;
                        await HandleClientAsync(server).ConfigureAwait(false);
                    }
                }
                catch (OperationCanceledException)
                {
                    break;
                }
                catch (Exception ex)
                {
                    Log.Error(ex, "Error accepting pipe connection");
                }
            }
        }

        private async Task HandleClientAsync(NamedPipeServerStream server)
        {
            try
            {
                var request = await PipeProtocol.ReadMessageAsync<PipeRequest>(server).ConfigureAwait(false);
                if (request == null)
                    return;

                PipeResponse response;

                switch (request.Type)
                {
                    case "shutdown":
                        response = PipeResponse.Ok("Shutting down");
                        await PipeProtocol.WriteMessageAsync(server, response).ConfigureAwait(false);
                        _shutdownCts.Cancel();
                        return;

                    case "status":
                        var status = new
                        {
                            galaxy = _galaxyName,
                            node = _nodeName,
                            connected = _connection?.IsConnected ?? false,
                            uptime = (DateTime.UtcNow - _lastActivity).ToString()
                        };
                        response = PipeResponse.Ok(JsonConvert.SerializeObject(status));
                        break;

                    case "execute":
                        response = await ExecuteCommandAsync(request).ConfigureAwait(false);
                        break;

                    default:
                        response = PipeResponse.Fail($"Unknown request type: {request.Type}");
                        break;
                }

                await PipeProtocol.WriteMessageAsync(server, response).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                Log.Error(ex, "Error handling client request");
            }
        }

        private async Task<PipeResponse> ExecuteCommandAsync(PipeRequest request)
        {
            try
            {
                return await _staThread.RunAsync<PipeResponse>(() =>
                {
                    var output = GRAccessCommandDispatcher.Execute(
                        _connection.Galaxy,
                        request.Command,
                        request.Subcommand,
                        request.Args);

                    return PipeResponse.Ok(output);
                }).ConfigureAwait(false);
            }
            catch (NotSupportedException ex)
            {
                return PipeResponse.Fail(ex.Message, 2);
            }
            catch (Exception ex)
            {
                return PipeResponse.Fail($"Execution error: {ex.Message}");
            }
        }

        private void CheckIdleTimeout()
        {
            if (DateTime.UtcNow - _lastActivity > _idleTimeout)
            {
                Log.Information("Idle timeout reached ({Timeout} min), shutting down",
                    _idleTimeout.TotalMinutes);
                _shutdownCts.Cancel();
            }
        }

        public void Dispose()
        {
            _shutdownCts?.Cancel();
            _idleTimer?.Dispose();
            _shutdownCts?.Dispose();
        }
    }
}
