using System.Diagnostics;
using Microsoft.Extensions.Logging;
using System.Globalization;
using System.Text;

namespace SteUp.Maui.Core.Logger;

public class FileLogger : ILogger
{
    private readonly string _path;
    private readonly string _fileNamePrefix;
    private readonly string _categoryName;
    private readonly Lock _lock = new();
    private readonly int _retentionDays;
    private string? _currentFileName;
    private DateTime _currentFileDate;
    private DateTime _lastCleanupDate;

    public FileLogger(string path, string fileNamePrefix, string categoryName, int retentionDays = 60)
    {
        _path = path;
        _fileNamePrefix = fileNamePrefix;
        _retentionDays = retentionDays;
        _lastCleanupDate = DateTime.MinValue;
        _categoryName = categoryName;

        if (!Directory.Exists(path))
            Directory.CreateDirectory(path);

        UpdateCurrentFileName();
        TryCleanOldLogs();
    }

    /// <summary>
    /// Elimina i log più vecchi di <see cref="_retentionDays"/> giorni.
    /// Viene eseguita al massimo una volta al giorno.
    /// </summary>
    private void ClearOldLogs()
    {
        try
        {
            var cutoff = DateTime.Now.Date.AddDays(-_retentionDays);
            var logFiles = Directory.GetFiles(_path, $"{_fileNamePrefix}-*.log");

            foreach (var file in logFiles)
            {
                try
                {
                    var fileName = Path.GetFileNameWithoutExtension(file);

                    var datePart = fileName[(_fileNamePrefix.Length + 1)..];

                    if (!DateTime.TryParseExact(datePart, "yyyy-MM-dd",
                            CultureInfo.InvariantCulture,
                            DateTimeStyles.None,
                            out var fileDate) || fileDate >= cutoff) continue;
                    File.Delete(file);
                    Debug.WriteLine($"[FileLogger] Log eliminato: {file}");
                }
                catch (Exception ex)
                {
                    Debug.WriteLine($"[FileLogger] Errore durante l'eliminazione del file {file}: {ex.Message}");
                }
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine($"[FileLogger] Errore durante la pulizia dei log: {ex.Message}");
        }
    }

    /// <summary>
    /// Esegue la pulizia dei log solo se non è già stata eseguita oggi.
    /// </summary>
    private void TryCleanOldLogs()
    {
        var today = DateTime.Now.Date;
        if (_lastCleanupDate == today) return;
        _lastCleanupDate = today;
        ClearOldLogs();
    }

    private void UpdateCurrentFileName()
    {
        var today = DateTime.Now.Date;
        if (_currentFileName != null && _currentFileDate == today) return;
        _currentFileDate = today;
        _currentFileName = $"{_fileNamePrefix}-{today:yyyy-MM-dd}.log";
    }

    public IDisposable? BeginScope<TState>(TState state) => null;

    public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None;

    public void Log<TState>(
        LogLevel logLevel,
        EventId eventId,
        TState state,
        Exception? exception,
        Func<TState, Exception?, string> formatter)
    {
        if (!IsEnabled(logLevel))
            return;

        try
        {
            lock (_lock)
            {
                UpdateCurrentFileName();
                TryCleanOldLogs();

                if (_currentFileName == null) return;

                var fullPath = Path.Combine(_path, _currentFileName);
                var logEntry = BuildLogEntry(logLevel, eventId, state, exception, formatter);

                File.AppendAllText(fullPath, logEntry + Environment.NewLine + Environment.NewLine);
                Debug.WriteLine($"[FileLogger] {logEntry}");
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine($"[FileLogger] Errore durante la scrittura del log: {ex}");
        }
    }

    private string BuildLogEntry<TState>(
        LogLevel logLevel,
        EventId eventId,
        TState state,
        Exception? exception,
        Func<TState, Exception?, string> formatter)
    {
        var sb = new StringBuilder();

        sb.Append($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}]");
        sb.Append($" [{GetLogLevelShort(logLevel)}]");
        sb.Append($" [{_categoryName}]");

        if (eventId.Id != 0 || !string.IsNullOrEmpty(eventId.Name))
            sb.Append($" [{eventId}]");

        sb.Append($" {formatter(state, exception)}");

        if (exception != null)
            AppendException(sb, exception);

        return sb.ToString();
    }

    private static void AppendException(StringBuilder sb, Exception exception, int depth = 0)
    {
        while (true)
        {
            var indent = depth == 0 ? "" : "  Inner ";
            sb.AppendLine();
            sb.Append($"{indent}Exception: {exception.GetType().FullName}: {exception.Message}");
            if (!string.IsNullOrWhiteSpace(exception.StackTrace))
            {
                sb.AppendLine();
                sb.Append($"{indent}StackTrace: {exception.StackTrace.Trim()}");
            }

            if (exception.InnerException != null)
            {
                exception = exception.InnerException;
                depth += 1;
                continue;
            }

            break;
        }
    }

    private static string GetLogLevelShort(LogLevel level) => level switch
    {
        LogLevel.Trace => "TRC",
        LogLevel.Debug => "DBG",
        LogLevel.Information => "INF",
        LogLevel.Warning => "WRN",
        LogLevel.Error => "ERR",
        LogLevel.Critical => "CRT",
        _ => "???"
    };
}