using EonaCat.LogStack.Core;
using EonaCat.LogStack.Flows;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ServiceMonitoring
{
    public enum ServiceType
    {
        TCP = 0,
        UDP,
        HTTP,
        HTTPS,
        File
    }

    public class ServiceStatus
    {
        public string ServiceName { get; set; }
        public string Host { get; set; }
        public int Port { get; set; }
        public string Status { get; set; }
        public DateTime LastChecked { get; set; }
        public ServiceType ServiceType { get; set; }
        public string AdditionalInfo { get; set; }
    }

    public sealed class StatusFlow : FlowBase
    {
        public event EventHandler<string> OnDirectoryException;
        private readonly List<ServiceStatus> _servicesToMonitor;
        private readonly TimeSpan _checkInterval;
        private readonly string _statusDirectory;
        private readonly CancellationTokenSource _cts;
        private readonly Action<ServiceStatus> _statusChangeTrigger;

        /// <summary>
        /// Log fileSize (default: 10 MB)
        /// </summary>
        public int MaxLogFileSize { get; set; }  = 10 * 1024 * 1024; // 10 MB

        /// <summary>
        /// Max Log files (default: 5)
        /// </summary>
        public int MaxLogFiles { get; set; } = 5;

        /// <summary>
        /// Default interval in minutes (default: 5)
        /// </summary>
        public int DefaultIntervalCheckInMinutes { get; set; } = 5;

        public StatusFlow(
            List<ServiceStatus> servicesToMonitor,
            TimeSpan? checkInterval,
            string statusDirectory,
            Action<ServiceStatus> statusChangeTrigger,
            LogLevel minimumLevel = LogLevel.Trace
        ) : base("StatusFlow", minimumLevel)
        {
            _servicesToMonitor = servicesToMonitor;

            if (checkInterval == null)
            {
                checkInterval = TimeSpan.FromMinutes(DefaultIntervalCheckInMinutes);
            }

            _checkInterval = checkInterval.Value;
            _statusChangeTrigger = statusChangeTrigger;

            if (string.IsNullOrWhiteSpace(statusDirectory))
            {
                statusDirectory = "./logs/status";
            }

            // Resolve relative path
            if (statusDirectory.StartsWith("./", StringComparison.Ordinal))
            {
                statusDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, statusDirectory.Substring(2));
            }

            try
            {
                Directory.CreateDirectory(statusDirectory);
            }
            catch
            {
                try
                {
                    var processId = Process.GetCurrentProcess().Id;
                    var newDirectory = Path.Combine(Path.GetTempPath(), "EonaCat.LogStack", processId.ToString());
                    Directory.CreateDirectory(newDirectory);
                    OnDirectoryException?.Invoke(this, $"StatusFlow: Could not create directory: '{statusDirectory}', using directory '{newDirectory}' instead");
                    statusDirectory = newDirectory;
                }
                catch
                {
                    var newDirectory = Path.GetTempPath();
                    OnDirectoryException?.Invoke(this, $"StatusFlow: Could not create directory: '{statusDirectory}', using directory '{newDirectory}' instead");
                    statusDirectory = newDirectory;

                    // Last resort: disable file output by pointing to a safe-ish temp path.
                    // The writer thread still runs and swallows failures.
                }
            }
            _statusDirectory = statusDirectory;

            _cts = new CancellationTokenSource();
            StartMonitoring();
        }

        public void StartMonitoring()
        {
            Task.Run(async () =>
            {
                while (IsEnabled && !_cts.Token.IsCancellationRequested)
                {
                    await MonitorServicesAsync();
                    await Task.Delay(_checkInterval, _cts.Token);
                }
            });
        }

        private async Task MonitorServicesAsync()
        {
            foreach (var service in _servicesToMonitor)
            {
                bool isServiceAvailable = service.ServiceType switch
                {
                    ServiceType.TCP => await IsTcpServiceAvailableAsync(service.Host, service.Port),
                    ServiceType.UDP => await IsUdpServiceAvailableAsync(service.Host, service.Port),
                    ServiceType.HTTP => await IsHttpServiceAvailableAsync(service.Host),
                    ServiceType.HTTPS => await IsHttpsServiceAvailableAsync(service),
                    ServiceType.File => await IsFileAvailableAsync(service.Host),
                    _ => false
                };

                if (isServiceAvailable != (service.Status == "Available"))
                {
                    service.Status = isServiceAvailable ? "Available" : "Unavailable";
                    service.LastChecked = DateTime.UtcNow;

                    // Trigger action when service status changes
                    _statusChangeTrigger?.Invoke(service);

                    // Log the status
                    LogServiceStatusToFile(service);
                }
            }
        }

        public override async Task<WriteResult> BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
        {
            Interlocked.Increment(ref DroppedCount);
            return WriteResult.NoBlastZone;
        }

        public override async Task<WriteResult> BlastBatchAsync(ReadOnlyMemory<LogEvent> logEvents, CancellationToken cancellationToken = default)
        {
            Interlocked.Increment(ref DroppedCount);
            return WriteResult.NoBlastZone;
        }

        private async Task<bool> IsTcpServiceAvailableAsync(string host, int port)
        {
            try
            {
                using (var tcpClient = new TcpClient())
                {
                    await tcpClient.ConnectAsync(host, port);
                    return true;
                }
            }
            catch
            {
                return false;
            }
        }

        private async Task<bool> IsUdpServiceAvailableAsync(string host, int port)
        {
            try
            {
                using (var udpClient = new UdpClient())
                {
                    var timeout = TimeSpan.FromSeconds(5);

                    var message = Encoding.ASCII.GetBytes("ping");
                    var sendTask = udpClient.SendAsync(message, message.Length, host, port);

                    var completedTask = await Task.WhenAny(sendTask, Task.Delay(timeout));
                    if (completedTask == sendTask)
                    {
                        var receiveTask = udpClient.ReceiveAsync();
                        var completedReceiveTask = await Task.WhenAny(receiveTask, Task.Delay(timeout));
                        return completedReceiveTask == receiveTask;
                    }
                    return false;
                }
            }
            catch
            {
                return false;
            }
        }

        private async Task<bool> IsHttpServiceAvailableAsync(string host)
        {
            try
            {
                var request = (HttpWebRequest)WebRequest.Create($"http://{host}");
                request.Method = "HEAD";
                using (var response = await request.GetResponseAsync())
                {
                    return true;
                }
            }
            catch
            {
                return false;
            }
        }

        public async Task<bool> IsHttpsServiceAvailableAsync(ServiceStatus service)
        {
            try
            {
                var handler = new HttpClientHandler
                {
                    ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) =>
                    {
                        if (sslPolicyErrors != System.Net.Security.SslPolicyErrors.None)
                        {
                            service.AdditionalInfo = $"Certificate Subject: {cert.Subject}\n" +
                                                     $"Certificate Issuer: {cert.Issuer}\n" +
                                                     $"Certificate Expiry: {cert.GetExpirationDateString()}\n" +
                                                     $"SSL Policy Errors: {sslPolicyErrors}\n";

                            foreach (var chainElement in chain.ChainElements)
                            {
                                service.AdditionalInfo += $"Chain Element: {chainElement.Certificate.Subject}, {chainElement.Certificate.Issuer}\n";
                            }
                        }
                        return sslPolicyErrors == System.Net.Security.SslPolicyErrors.None;
                    }
                };

                using (var client = new HttpClient(handler))
                {
                    var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, $"https://{service.Host}"));
                    return response.IsSuccessStatusCode;
                }
            }
            catch (Exception ex)
            {
                service.AdditionalInfo = $"Error: {ex.Message}";
                return false;
            }
        }

        private async Task<bool> IsFileAvailableAsync(string filePath)
        {
            try
            {
                return File.Exists(filePath);
            }
            catch
            {
                return false;
            }
        }

        private void LogServiceStatusToFile(ServiceStatus service)
        {
            string statusMessage = $"{service.ServiceName} ({service.Host}:{service.Port}) - Status: {service.Status}, Last Checked: {service.LastChecked}";

            if (!string.IsNullOrEmpty(service.AdditionalInfo))
            {
                statusMessage += $"\nAdditional Info: {service.AdditionalInfo}";
            }

            RollOverLogFileIfNeeded();

            try
            {
                string logFilePath = Path.Combine(_statusDirectory, "status_log.txt");
                using (var writer = new StreamWriter(logFilePath, append: true))
                {
                    writer.WriteLine(statusMessage);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"StatusFlow: Error writing to file: {ex.Message}");
            }
        }

        private void RollOverLogFileIfNeeded()
        {
            try
            {
                string logFilePath = Path.Combine(_statusDirectory, "status_log.txt");

                if (File.Exists(logFilePath) && new FileInfo(logFilePath).Length > MaxLogFileSize)
                {
                    var logFiles = Directory.GetFiles(_statusDirectory, "status_log_*.txt").OrderBy(f => f).ToList();

                    if (logFiles.Count >= MaxLogFiles)
                    {
                        File.Delete(logFiles[0]);
                        logFiles.RemoveAt(0);
                    }

                    string newLogFilePath = Path.Combine(_statusDirectory, $"status_log_{DateTime.UtcNow:yyyyMMdd_HHmmss}.txt");
                    File.Move(logFilePath, newLogFilePath);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"StatusFlow: Error handling log file rollover: {ex.Message}");
            }
        }

        public override async ValueTask DisposeAsync()
        {
            _cts.Cancel();
            await base.DisposeAsync();
        }

        public override Task FlushAsync(CancellationToken cancellationToken = default)
        {

            return Task.CompletedTask;
        }
    }
}