using System.IO;
using System.Net.Sockets;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System;
using System.Linq;

namespace EonaCat.LogStack.Server
{
    // This file is part of the EonaCat project(s) which is released under the Apache License.
    // See the LICENSE file or go to https://EonaCat.com/License for full license details.
    public class Server
    {
        private TcpListener _tcpListener;
        private UdpClient _udpListener;
        private CancellationTokenSource _cts;
        private bool _isRunning;
        private readonly bool _useUdp;
        private const long MaxLogFileSize = 200 * 1024 * 1024; // 200MB log rollover limit
        private readonly int _logRetentionDays; // Number of days to retain logs
        private readonly long _maxLogDirectorySize; // Maximum allowed size of the logs directory
        private const int UdpBufferSize = 65507; // Maximum UDP packet size (65507 bytes for UDP payload)

        /// <summary>
        /// EonaCat Log Server
        /// </summary>
        /// <param name="useUdp">Determine if we need to start a udp server (default: true)</param>
        /// <param name="logRetentionDays">Max log retention days (default: 30)</param>
        /// <param name="maxLogDirectorySize">Max log directory size (default: 10GB)</param>
        public Server(bool useUdp = true, int logRetentionDays = 30, long maxLogDirectorySize = 10L * 1024 * 1024 * 1024) // Default 10GB max directory size
        {
            _useUdp = useUdp;
            _logRetentionDays = logRetentionDays;
            _maxLogDirectorySize = maxLogDirectorySize;
        }

        protected virtual Task ProcessLogAsync(string logData)
        {
            string logsRootDirectory = "logs";

            // Create root log directory if it doesn't exist
            if (!Directory.Exists(logsRootDirectory))
            {
                Directory.CreateDirectory(logsRootDirectory);
            }

            // Create a daily directory for logs
            string dailyLogsDirectory = Path.Combine(logsRootDirectory, DateTime.Now.ToString("yyyyMMdd"));
            if (!Directory.Exists(dailyLogsDirectory))
            {
                Directory.CreateDirectory(dailyLogsDirectory);
            }

            // Base log file name
            string baseLogFilePath = Path.Combine(dailyLogsDirectory, "EonaCatLogs");
            string logFilePath = baseLogFilePath + ".log";

            int fileIndex = 1;
            while (File.Exists(logFilePath) && new FileInfo(logFilePath).Length > MaxLogFileSize)
            {
                logFilePath = baseLogFilePath + $"_{fileIndex}.log";
                fileIndex++;
            }

            // After processing log, check directory size and clean up if needed
            CleanUpOldLogs();

            return File.AppendAllTextAsync(logFilePath, logData + Environment.NewLine);
        }

        private void CleanUpOldLogs()
        {
            string logsRootDirectory = "logs";
            if (!Directory.Exists(logsRootDirectory))
            {
                return;
            }

            // Delete old directories
            foreach (var directory in Directory.GetDirectories(logsRootDirectory))
            {
                try
                {
                    DirectoryInfo dirInfo = new DirectoryInfo(directory);
                    if (dirInfo.CreationTime < DateTime.Now.AddDays(-_logRetentionDays))
                    {
                        Console.WriteLine($"Deleting old log directory: {directory}");
                        Directory.Delete(directory, true); // Delete directory and its contents
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Error deleting old directory {directory}: {ex.Message}");
                }
            }

            // Ensure total size of log directory doesn't exceed max limit
            long totalDirectorySize = GetDirectorySize(logsRootDirectory);
            if (totalDirectorySize > _maxLogDirectorySize)
            {
                Console.WriteLine("Log directory size exceeded limit, cleaning up...");

                // Delete the oldest directories until the size limit is met
                foreach (var directory in Directory.GetDirectories(logsRootDirectory).OrderBy(d => new DirectoryInfo(d).CreationTime))
                {
                    try
                    {
                        DirectoryInfo dirInfo = new DirectoryInfo(directory);
                        long dirSize = GetDirectorySize(directory);
                        totalDirectorySize -= dirSize;

                        // Delete the directory if the total size exceeds the limit
                        Directory.Delete(directory, true);
                        Console.WriteLine($"Deleted directory: {directory}");

                        // Stop deleting if we are under the size limit
                        if (totalDirectorySize <= _maxLogDirectorySize)
                        {
                            break;
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"Error deleting directory {directory}: {ex.Message}");
                    }
                }
            }
        }

        private long GetDirectorySize(string directory)
        {
            long size = 0;
            try
            {
                // Add size of files in the directory
                size += Directory.GetFiles(directory).Sum(file => new FileInfo(file).Length);

                // Add size of files in subdirectories
                foreach (var subdirectory in Directory.GetDirectories(directory))
                {
                    size += GetDirectorySize(subdirectory);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error calculating size for directory {directory}: {ex.Message}");
            }
            return size;
        }

        public async Task Start(IPAddress ipAddress = null, int port = 5555)
        {
            if (ipAddress == null)
            {
                ipAddress = IPAddress.Any;
            }

            _cts = new CancellationTokenSource();
            _isRunning = true;

            if (_useUdp)
            {
                _udpListener = new UdpClient(port);
                Console.WriteLine($"EonaCat UDP Log Server started on port {port}...");
                await ListenUdpAsync();
            }
            else
            {
                _tcpListener = new TcpListener(ipAddress, port);
                _tcpListener.Start();
                Console.WriteLine($"EonaCat TCP Log Server started on port {port}...");
                await ListenTcpAsync();
            }
        }

        private async Task ListenTcpAsync()
        {
            try
            {
                while (!_cts.Token.IsCancellationRequested)
                {
                    TcpClient client = await _tcpListener.AcceptTcpClientAsync();
                    _ = Task.Run(() => HandleTcpClient(client));
                }
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("TCP Server stopping...");
            }
        }

        private async Task ListenUdpAsync()
        {
            try
            {
                while (!_cts.Token.IsCancellationRequested)
                {
                    // Increased buffer size for UDP
                    UdpReceiveResult result = await _udpListener.ReceiveAsync();
                    string logData = Encoding.UTF8.GetString(result.Buffer);

                    // If the received data is too large, process it in chunks
                    if (result.Buffer.Length > UdpBufferSize)
                    {
                        // Handle fragmentation and reassembly (this is a basic placeholder logic)
                        Console.WriteLine("Received large UDP data. Handling fragmentation.");
                        await ProcessLargeDataAsync(result.Buffer);
                    }
                    else
                    {
                        Console.WriteLine($"Received UDP Log: {logData}");
                        await ProcessLogAsync(logData);
                    }
                }
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("UDP Server stopping...");
            }
        }

        private async Task ProcessLargeDataAsync(byte[] data)
        {
            // You can implement your own logic here for processing large UDP data, such as fragmentation handling
            string largeDataString = Encoding.UTF8.GetString(data);
            await ProcessLogAsync(largeDataString);
        }

        public void Stop()
        {
            if (_isRunning)
            {
                _cts.Cancel();

                // Proper cleanup of resources
                _cts.Dispose();
                if (_useUdp)
                {
                    _udpListener?.Close();
                    _udpListener?.Dispose();
                }
                else
                {
                    _tcpListener?.Stop();
                    _tcpListener?.Server?.Dispose(); // Dispose of the socket (if any)
                }

                _isRunning = false;
                Console.WriteLine("EonaCat Log Server stopped.");
            }
        }

        private async Task HandleTcpClient(TcpClient client)
        {
            try
            {
                using (NetworkStream stream = client.GetStream())
                using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
                {
                    char[] buffer = new char[8192]; // 8KB buffer size for large data
                    int bytesRead;
                    StringBuilder logData = new StringBuilder();

                    while ((bytesRead = await reader.ReadAsync(buffer, 0, buffer.Length)) > 0)
                    {
                        logData.Append(new string(buffer, 0, bytesRead));
                    }

                    Console.WriteLine($"Received TCP Log: {logData.ToString()}");
                    await ProcessLogAsync(logData.ToString());
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error: {ex.Message}");
            }
            finally
            {
                // Ensure client is properly disposed
                client.Close();
                client.Dispose();
            }
        }
    }
}