using System.Collections.Concurrent;
using System.Text;

namespace ClawdDotNet.Core.Logging;

/// <summary>
/// Schreibt Log-Einträge asynchron in datei- und datumsgetrennte Logfiles.
/// Struktur: {LogDirectory}/{Datum}/{Modul}.log
/// Thread-safe durch ConcurrentQueue + dediziertem Writer-Task.
/// </summary>
public sealed class FileLogWriter : IAsyncDisposable
{
    private readonly FileLoggerOptions _options;
    private readonly ConcurrentQueue<LogEntry> _queue = new();
    private readonly SemaphoreSlim _signal = new(0);
    private readonly CancellationTokenSource _cts = new();
    private readonly Task _writerTask;
    private readonly ConcurrentDictionary<string, Lock> _fileLocks = new();

    public FileLogWriter(FileLoggerOptions options)
    {
        _options = options;
        Directory.CreateDirectory(_options.LogDirectory);
        _writerTask = Task.Run(ProcessQueueAsync);
    }

    public void Enqueue(LogEntry entry)
    {
        if (entry.Level < _options.MinimumLevel)
            return;

        _queue.Enqueue(entry);
        _signal.Release();
    }

    private async Task ProcessQueueAsync()
    {
        while (!_cts.Token.IsCancellationRequested)
        {
            try
            {
                await _signal.WaitAsync(_cts.Token);
            }
            catch (OperationCanceledException)
            {
                break;
            }

            DrainQueue();
        }

        DrainQueue();
    }

    private void DrainQueue()
    {
        while (_queue.TryDequeue(out var entry))
        {
            try
            {
                WriteEntry(entry);
            }
            catch
            {
                // Logging darf die Anwendung niemals crashen
            }
        }
    }

    private void WriteEntry(LogEntry entry)
    {
        var dateDir = Path.Combine(
            _options.LogDirectory,
            entry.Timestamp.ToString(_options.DateFormat));

        Directory.CreateDirectory(dateDir);

        var safeModule = SanitizeModuleName(entry.Module);
        var filePath = Path.Combine(dateDir, $"{safeModule}.log");

        var fileLock = _fileLocks.GetOrAdd(filePath, _ => new Lock());

        lock (fileLock)
        {
            var line = entry.Format(_options.TimestampFormat) + Environment.NewLine;
            File.AppendAllText(filePath, line, Encoding.UTF8);
        }
    }

    private static string SanitizeModuleName(string module)
    {
        var sanitized = module
            .Replace('.', '_')
            .Replace('/', '_')
            .Replace('\\', '_');

        foreach (var c in Path.GetInvalidFileNameChars())
            sanitized = sanitized.Replace(c, '_');

        return string.IsNullOrWhiteSpace(sanitized) ? "Unknown" : sanitized;
    }

    public async ValueTask DisposeAsync()
    {
        await _cts.CancelAsync();

        try
        {
            await _writerTask;
        }
        catch (OperationCanceledException)
        {
        }

        _signal.Dispose();
        _cts.Dispose();
    }
}
