using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Core.Interceptors;
using Serilog;
using ZB.MOM.WW.LmxProxy.Host.Configuration;
using ZB.MOM.WW.LmxProxy.Host.Domain;
using ZB.MOM.WW.LmxProxy.Host.Grpc.Services;
using ZB.MOM.WW.LmxProxy.Host.Implementation;
using ZB.MOM.WW.LmxProxy.Host.Security;
using ZB.MOM.WW.LmxProxy.Host.Services;
using ZB.MOM.WW.LmxProxy.Host.Grpc;
using ConnectionState = ZB.MOM.WW.LmxProxy.Host.Domain.ConnectionState;

namespace ZB.MOM.WW.LmxProxy.Host
{
    /// <summary>
    ///     Windows service that hosts the gRPC server and MxAccess client.
    ///     Manages lifecycle of gRPC server, SCADA client, subscription manager, and API key service.
    /// </summary>
    public class LmxProxyService
    {
        private static readonly ILogger Logger = Log.ForContext<LmxProxyService>();
        private readonly LmxProxyConfiguration _configuration;
        private readonly SemaphoreSlim _reconnectSemaphore = new(1, 1);
        private readonly Func<LmxProxyConfiguration, IScadaClient> _scadaClientFactory;
        private readonly CancellationTokenSource _shutdownCts = new();
        private ApiKeyService? _apiKeyService;
        private Task? _connectionMonitorTask;
        private DetailedHealthCheckService? _detailedHealthCheckService;

        private Server? _grpcServer;
        private HealthCheckService? _healthCheckService;
        private PerformanceMetrics? _performanceMetrics;
        private IScadaClient? _scadaClient;
        private SessionManager? _sessionManager;
        private StatusReportService? _statusReportService;
        private StatusWebServer? _statusWebServer;
        private SubscriptionManager? _subscriptionManager;

        /// <summary>
        ///     Initializes a new instance of the <see cref="LmxProxyService" /> class.
        /// </summary>
        /// <param name="configuration">Configuration settings for the service.</param>
        /// <exception cref="ArgumentNullException">Thrown if configuration is null.</exception>
        public LmxProxyService(LmxProxyConfiguration configuration,
            Func<LmxProxyConfiguration, IScadaClient>? scadaClientFactory = null)
        {
            _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
            _scadaClientFactory = scadaClientFactory ?? (config => new MxAccessClient(config.Connection));
        }

        /// <summary>
        ///     Starts the LmxProxy service, initializing all required components and starting the gRPC server.
        /// </summary>
        /// <returns><c>true</c> if the service started successfully; otherwise, <c>false</c>.</returns>
        public bool Start()
        {
            try
            {
                Logger.Information("Starting LmxProxy service on port {Port}", _configuration.GrpcPort);

                // Validate configuration before proceeding
                if (!ValidateConfiguration())
                {
                    Logger.Error("Configuration validation failed");
                    return false;
                }

                // Check and ensure TLS certificates are valid
                if (_configuration.Tls.Enabled)
                {
                    Logger.Information("Checking TLS certificate configuration");
                    var tlsManager = new TlsCertificateManager(_configuration.Tls);
                    if (!tlsManager.EnsureCertificatesValid())
                    {
                        Logger.Error("Failed to ensure valid TLS certificates");
                        throw new InvalidOperationException("TLS certificate validation or generation failed");
                    }

                    Logger.Information("TLS certificates validated successfully");
                }

                // Create performance metrics service
                _performanceMetrics = new PerformanceMetrics();
                Logger.Information("Performance metrics service initialized");

                // Create API key service
                string apiKeyConfigPath = Path.GetFullPath(_configuration.ApiKeyConfigFile);
                _apiKeyService = new ApiKeyService(apiKeyConfigPath);
                Logger.Information("API key service initialized with config file: {ConfigFile}", apiKeyConfigPath);

                // Create SCADA client via factory
                _scadaClient = _scadaClientFactory(_configuration) ??
                               throw new InvalidOperationException("SCADA client factory returned null.");

                // Subscribe to connection state changes
                _scadaClient.ConnectionStateChanged += OnConnectionStateChanged;

                // Automatically connect to MxAccess on startup
                try
                {
                    Logger.Information("Connecting to MxAccess...");
                    Task connectTask = _scadaClient.ConnectAsync();
                    if (!connectTask.Wait(TimeSpan.FromSeconds(_configuration.Connection.ConnectionTimeoutSeconds)))
                    {
                        throw new TimeoutException(
                            $"Timeout connecting to MxAccess after {_configuration.Connection.ConnectionTimeoutSeconds} seconds");
                    }

                    Logger.Information("Successfully connected to MxAccess");
                }
                catch (Exception ex)
                {
                    Logger.Error(ex, "Failed to connect to MxAccess on startup");
                    throw;
                }

                // Start connection monitoring if auto-reconnect is enabled
                if (_configuration.Connection.AutoReconnect)
                {
                    _connectionMonitorTask = Task.Run(() => MonitorConnectionAsync(_shutdownCts.Token));
                    Logger.Information("Connection monitoring started with {Interval} second interval",
                        _configuration.Connection.MonitorIntervalSeconds);
                }

                // Create subscription manager with configuration
                _subscriptionManager = new SubscriptionManager(_scadaClient, _configuration.Subscription);

                // Create session manager for tracking client sessions
                _sessionManager = new SessionManager();
                Logger.Information("Session manager initialized");

                // Create health check services
                _healthCheckService = new HealthCheckService(_scadaClient, _subscriptionManager, _performanceMetrics);
                _detailedHealthCheckService = new DetailedHealthCheckService(_scadaClient);
                Logger.Information("Health check services initialized");

                // Create status report service and web server
                _statusReportService = new StatusReportService(
                    _scadaClient,
                    _subscriptionManager,
                    _performanceMetrics,
                    _healthCheckService,
                    _detailedHealthCheckService);

                _statusWebServer = new StatusWebServer(_configuration.WebServer, _statusReportService);
                Logger.Information("Status web server initialized");

                // Create gRPC service with session manager and performance metrics
                var scadaService = new ScadaGrpcService(_scadaClient, _subscriptionManager, _sessionManager, _performanceMetrics);

                // Create API key interceptor
                var apiKeyInterceptor = new ApiKeyInterceptor(_apiKeyService);

                // Configure server credentials based on TLS configuration
                ServerCredentials serverCredentials;
                if (_configuration.Tls.Enabled)
                {
                    serverCredentials = CreateTlsCredentials(_configuration.Tls);
                    Logger.Information("TLS enabled for gRPC server");
                }
                else
                {
                    serverCredentials = ServerCredentials.Insecure;
                    Logger.Warning("gRPC server running without TLS encryption - not recommended for production");
                }

                // Configure and start gRPC server with interceptor
                _grpcServer = new Server
                {
                    Services = { ScadaService.BindService(scadaService).Intercept(apiKeyInterceptor) },
                    Ports = { new ServerPort("0.0.0.0", _configuration.GrpcPort, serverCredentials) }
                };

                _grpcServer.Start();

                string securityMode = _configuration.Tls.Enabled ? "TLS/SSL" : "INSECURE";
                Logger.Information("LmxProxy service started successfully on port {Port} ({SecurityMode})",
                    _configuration.GrpcPort, securityMode);
                Logger.Information("gRPC server listening on 0.0.0.0:{Port}", _configuration.GrpcPort);

                // Start status web server
                if (_statusWebServer != null && !_statusWebServer.Start())
                {
                    Logger.Warning("Failed to start status web server, continuing without it");
                }

                return true;
            }
            catch (Exception ex)
            {
                Logger.Fatal(ex, "Failed to start LmxProxy service");
                return false;
            }
        }

        /// <summary>
        ///     Stops the LmxProxy service, shutting down the gRPC server and disposing all resources.
        /// </summary>
        /// <returns><c>true</c> if the service stopped successfully; otherwise, <c>false</c>.</returns>
        public bool Stop()
        {
            try
            {
                Logger.Information("Stopping LmxProxy service");

                _shutdownCts.Cancel();

                // Stop connection monitoring
                if (_connectionMonitorTask != null)
                {
                    try
                    {
                        _connectionMonitorTask.Wait(TimeSpan.FromSeconds(5));
                    }
                    catch (Exception ex)
                    {
                        Logger.Warning(ex, "Error stopping connection monitor");
                    }
                }

                // Shutdown gRPC server
                if (_grpcServer != null)
                {
                    Logger.Information("Shutting down gRPC server");
                    Task? shutdownTask = _grpcServer.ShutdownAsync();

                    // Wait up to 10 seconds for graceful shutdown
                    if (!shutdownTask.Wait(TimeSpan.FromSeconds(10)))
                    {
                        Logger.Warning("gRPC server shutdown timeout, forcing kill");
                        _grpcServer.KillAsync().Wait(TimeSpan.FromSeconds(5));
                    }

                    _grpcServer = null;
                }

                // Stop status web server
                if (_statusWebServer != null)
                {
                    Logger.Information("Stopping status web server");
                    try
                    {
                        _statusWebServer.Stop();
                        _statusWebServer.Dispose();
                        _statusWebServer = null;
                    }
                    catch (Exception ex)
                    {
                        Logger.Warning(ex, "Error stopping status web server");
                    }
                }

                // Dispose status report service
                if (_statusReportService != null)
                {
                    Logger.Information("Disposing status report service");
                    _statusReportService = null;
                }

                // Dispose health check services
                if (_detailedHealthCheckService != null)
                {
                    Logger.Information("Disposing detailed health check service");
                    _detailedHealthCheckService = null;
                }

                if (_healthCheckService != null)
                {
                    Logger.Information("Disposing health check service");
                    _healthCheckService = null;
                }

                // Dispose subscription manager
                if (_subscriptionManager != null)
                {
                    Logger.Information("Disposing subscription manager");
                    _subscriptionManager.Dispose();
                    _subscriptionManager = null;
                }

                // Dispose session manager
                if (_sessionManager != null)
                {
                    Logger.Information("Disposing session manager");
                    _sessionManager.Dispose();
                    _sessionManager = null;
                }

                // Dispose API key service
                if (_apiKeyService != null)
                {
                    Logger.Information("Disposing API key service");
                    _apiKeyService.Dispose();
                    _apiKeyService = null;
                }

                // Dispose performance metrics
                if (_performanceMetrics != null)
                {
                    Logger.Information("Disposing performance metrics service");
                    _performanceMetrics.Dispose();
                    _performanceMetrics = null;
                }

                // Disconnect and dispose SCADA client
                if (_scadaClient != null)
                {
                    Logger.Information("Disconnecting SCADA client");

                    // Unsubscribe from events
                    _scadaClient.ConnectionStateChanged -= OnConnectionStateChanged;

                    try
                    {
                        Task disconnectTask = _scadaClient.DisconnectAsync();
                        if (!disconnectTask.Wait(TimeSpan.FromSeconds(10)))
                        {
                            Logger.Warning("SCADA client disconnect timeout");
                        }
                    }
                    catch (Exception ex)
                    {
                        Logger.Warning(ex, "Error disconnecting SCADA client");
                    }

                    try
                    {
                        Task? disposeTask = _scadaClient.DisposeAsync().AsTask();
                        if (!disposeTask.Wait(TimeSpan.FromSeconds(5)))
                        {
                            Logger.Warning("SCADA client dispose timeout");
                        }
                    }
                    catch (Exception ex)
                    {
                        Logger.Warning(ex, "Error disposing SCADA client");
                    }

                    _scadaClient = null;
                }

                Logger.Information("LmxProxy service stopped successfully");
                return true;
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Error stopping LmxProxy service");
                return false;
            }
        }

        /// <summary>
        ///     Pauses the LmxProxy service. No operation is performed except logging.
        /// </summary>
        public void Pause() => Logger.Information("LmxProxy service paused");

        /// <summary>
        ///     Continues the LmxProxy service after a pause. No operation is performed except logging.
        /// </summary>
        public void Continue() => Logger.Information("LmxProxy service continued");

        /// <summary>
        ///     Requests shutdown of the LmxProxy service and stops all components.
        /// </summary>
        public void Shutdown()
        {
            Logger.Information("LmxProxy service shutdown requested");
            Stop();
        }

        /// <summary>
        ///     Handles connection state changes from the SCADA client.
        /// </summary>
        private void OnConnectionStateChanged(object? sender, ConnectionStateChangedEventArgs e)
        {
            Logger.Information("MxAccess connection state changed from {Previous} to {Current}",
                e.PreviousState, e.CurrentState);

            if (e.CurrentState == ConnectionState.Disconnected &&
                e.PreviousState == ConnectionState.Connected)
            {
                Logger.Warning("MxAccess connection lost. Automatic reconnection will be attempted.");
            }
        }

        /// <summary>
        ///     Monitors the connection and attempts to reconnect when disconnected.
        /// </summary>
        private async Task MonitorConnectionAsync(CancellationToken cancellationToken)
        {
            Logger.Information("Starting connection monitor");

            while (!cancellationToken.IsCancellationRequested)
            {
                try
                {
                    await Task.Delay(TimeSpan.FromSeconds(_configuration.Connection.MonitorIntervalSeconds),
                        cancellationToken);

                    if (_scadaClient != null && !_scadaClient.IsConnected && !cancellationToken.IsCancellationRequested)
                    {
                        await _reconnectSemaphore.WaitAsync(cancellationToken);
                        try
                        {
                            if (_scadaClient != null && !_scadaClient.IsConnected)
                            {
                                Logger.Information("Attempting to reconnect to MxAccess...");

                                try
                                {
                                    await _scadaClient.ConnectAsync(cancellationToken);
                                    Logger.Information("Successfully reconnected to MxAccess");
                                }
                                catch (Exception ex)
                                {
                                    Logger.Warning(ex,
                                        "Failed to reconnect to MxAccess. Will retry in {Interval} seconds.",
                                        _configuration.Connection.MonitorIntervalSeconds);
                                }
                            }
                        }
                        finally
                        {
                            _reconnectSemaphore.Release();
                        }
                    }
                }
                catch (OperationCanceledException)
                {
                    // Expected when shutting down
                    break;
                }
                catch (Exception ex)
                {
                    Logger.Error(ex, "Error in connection monitor");
                }
            }

            Logger.Information("Connection monitor stopped");
        }

        /// <summary>
        ///     Creates TLS server credentials from configuration
        /// </summary>
        private static ServerCredentials CreateTlsCredentials(TlsConfiguration tlsConfig)
        {
            try
            {
                // Read certificate and key files
                string serverCert = File.ReadAllText(tlsConfig.ServerCertificatePath);
                string serverKey = File.ReadAllText(tlsConfig.ServerKeyPath);

                var keyCertPairs = new List<KeyCertificatePair>
                {
                    new(serverCert, serverKey)
                };

                // Configure client certificate requirements
                if (tlsConfig.RequireClientCertificate && !string.IsNullOrWhiteSpace(tlsConfig.ClientCaCertificatePath))
                {
                    string clientCaCert = File.ReadAllText(tlsConfig.ClientCaCertificatePath);
                    return new SslServerCredentials(
                        keyCertPairs,
                        clientCaCert,
                        tlsConfig.CheckCertificateRevocation
                            ? SslClientCertificateRequestType.RequestAndRequireAndVerify
                            : SslClientCertificateRequestType.RequestAndRequireButDontVerify);
                }

                if (tlsConfig.RequireClientCertificate)
                {
                    // Require client certificate but no CA specified - use system CA
                    return new SslServerCredentials(
                        keyCertPairs,
                        null,
                        SslClientCertificateRequestType.RequestAndRequireAndVerify);
                }

                // No client certificate required
                return new SslServerCredentials(keyCertPairs);
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Failed to create TLS credentials");
                throw new InvalidOperationException("Failed to configure TLS for gRPC server", ex);
            }
        }

        /// <summary>
        ///     Validates the service configuration and returns false if any critical issues are found
        /// </summary>
        private bool ValidateConfiguration()
        {
            try
            {
                // Validate gRPC port
                if (_configuration.GrpcPort <= 0 || _configuration.GrpcPort > 65535)
                {
                    Logger.Error("Invalid gRPC port: {Port}. Port must be between 1 and 65535",
                        _configuration.GrpcPort);
                    return false;
                }

                // Validate API key configuration file
                if (string.IsNullOrWhiteSpace(_configuration.ApiKeyConfigFile))
                {
                    Logger.Error("API key configuration file path is null or empty");
                    return false;
                }

                // Check if API key file exists or can be created
                string apiKeyPath = Path.GetFullPath(_configuration.ApiKeyConfigFile);
                string? apiKeyDirectory = Path.GetDirectoryName(apiKeyPath);

                if (!string.IsNullOrEmpty(apiKeyDirectory) && !Directory.Exists(apiKeyDirectory))
                {
                    try
                    {
                        Directory.CreateDirectory(apiKeyDirectory);
                    }
                    catch (Exception ex)
                    {
                        Logger.Error(ex, "Cannot create directory for API key file: {Directory}", apiKeyDirectory);
                        return false;
                    }
                }

                // If API key file exists, validate it can be read
                if (File.Exists(apiKeyPath))
                {
                    try
                    {
                        string content = File.ReadAllText(apiKeyPath);
                        if (!string.IsNullOrWhiteSpace(content))
                        {
                            // Try to parse as JSON to validate format
                            JsonDocument.Parse(content);
                        }
                    }
                    catch (Exception ex)
                    {
                        Logger.Error(ex, "API key configuration file is invalid or unreadable: {FilePath}", apiKeyPath);
                        return false;
                    }
                }

                // Validate TLS configuration if enabled
                if (_configuration.Tls.Enabled)
                {
                    if (!_configuration.Tls.Validate())
                    {
                        Logger.Error("TLS configuration validation failed");
                        return false;
                    }
                }

                // Validate web server configuration if enabled
                if (_configuration.WebServer.Enabled)
                {
                    if (_configuration.WebServer.Port <= 0 || _configuration.WebServer.Port > 65535)
                    {
                        Logger.Error("Invalid web server port: {Port}. Port must be between 1 and 65535",
                            _configuration.WebServer.Port);
                        return false;
                    }

                    // Check for port conflicts
                    if (_configuration.WebServer.Port == _configuration.GrpcPort)
                    {
                        Logger.Error("Web server port {WebPort} conflicts with gRPC port {GrpcPort}",
                            _configuration.WebServer.Port, _configuration.GrpcPort);
                        return false;
                    }
                }

                Logger.Information("Configuration validation passed");
                return true;
            }
            catch (Exception ex)
            {
                Logger.Error(ex, "Error during configuration validation");
                return false;
            }
        }
    }
}
