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

namespace ClawdDotNet.Services;

/// <summary>
/// Überwacht das Logs-Verzeichnis und liefert neue Log-Einträge gefiltert
/// an eine RichTextBox. Bereinigt die RichTextBox automatisch wenn sie
/// zu voll wird, damit das UI reaktionsfähig bleibt.
///
/// Struktur: Logs/{Datum}/{Modul}.log
/// Log-Format: [{Timestamp}] [{LEVEL}] {Message}
/// </summary>
public sealed class LiveLogViewerService : IDisposable
{
    private readonly string _logDirectory;
    private readonly RichTextBox _target;
    private readonly System.Windows.Forms.Timer _refreshTimer;
    private readonly int _maxLines;

    // Tracking: pro Datei die letzte gelesene Position
    private readonly ConcurrentDictionary<string, long> _filePositions = new();

    // Filter
    private string _moduleFilter = ""; // leer = alle
    private string _levelFilter = "";  // leer = alle

    private static readonly Regex LevelRegex = new(
        @"\[(INF|WRN|ERR|DBG)\]",
        RegexOptions.Compiled);

    public LiveLogViewerService(string logDirectory, RichTextBox target, int refreshIntervalMs = 500, int maxLines = 2000)
    {
        _logDirectory = logDirectory;
        _target = target;
        _maxLines = maxLines;

        _refreshTimer = new System.Windows.Forms.Timer { Interval = refreshIntervalMs };
        _refreshTimer.Tick += OnTimerTick;
    }

    public void Start() => _refreshTimer.Start();
    public void Stop() => _refreshTimer.Stop();

    public void SetModuleFilter(string module)
    {
        _moduleFilter = module;
        ClearAndResetPositions();
    }

    public void SetLevelFilter(string level)
    {
        _levelFilter = level;
        ClearAndResetPositions();
    }

    /// <summary>
    /// Gibt alle erkannten Modul-Namen zurück (basierend auf den vorhandenen .log-Dateien).
    /// </summary>
    public List<string> GetAvailableModules()
    {
        var modules = new HashSet<string> { "Alle" };

        if (!Directory.Exists(_logDirectory))
            return modules.ToList();

        // Alle Unterordner (Datum-Ordner) durchsuchen
        foreach (var dateDir in Directory.GetDirectories(_logDirectory))
        {
            foreach (var logFile in Directory.GetFiles(dateDir, "*.log"))
            {
                var name = Path.GetFileNameWithoutExtension(logFile);
                modules.Add(name);
            }
        }

        return modules.OrderBy(m => m == "Alle" ? "" : m).ToList();
    }

    private void OnTimerTick(object? sender, EventArgs e)
    {
        try
        {
            ReadNewEntries();
        }
        catch
        {
            // Logging-Viewer darf niemals das UI crashen
        }
    }

    private void ReadNewEntries()
    {
        if (!Directory.Exists(_logDirectory))
            return;

        // Heutiges Datum-Verzeichnis (und ggf. gestriges für Logs um Mitternacht)
        var today = DateTime.Now.ToString("yyyy-MM-dd");
        var todayDir = Path.Combine(_logDirectory, today);

        if (!Directory.Exists(todayDir))
            return;

        var logFiles = Directory.GetFiles(todayDir, "*.log");
        var newLines = new List<(DateTime time, string line, string module)>();

        foreach (var filePath in logFiles)
        {
            var moduleName = Path.GetFileNameWithoutExtension(filePath);

            // Modul-Filter
            if (!string.IsNullOrEmpty(_moduleFilter) && _moduleFilter != "Alle"
                && !string.Equals(moduleName, _moduleFilter, StringComparison.OrdinalIgnoreCase))
                continue;

            var lastPos = _filePositions.GetOrAdd(filePath, 0L);

            try
            {
                using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

                if (fs.Length < lastPos)
                {
                    // Datei wurde rotiert/gekürzt
                    lastPos = 0;
                }

                if (fs.Length == lastPos)
                    continue;

                fs.Seek(lastPos, SeekOrigin.Begin);
                using var reader = new StreamReader(fs);

                while (reader.ReadLine() is { } line)
                {
                    if (string.IsNullOrWhiteSpace(line))
                        continue;

                    // Level-Filter
                    if (!string.IsNullOrEmpty(_levelFilter) && _levelFilter != "Alle")
                    {
                        if (!PassesLevelFilter(line))
                            continue;
                    }

                    newLines.Add((DateTime.Now, $"[{moduleName}] {line}", moduleName));
                }

                _filePositions[filePath] = fs.Position;
            }
            catch (IOException)
            {
                // Datei wird gerade geschrieben - nächstes Mal versuchen
            }
        }

        if (newLines.Count == 0)
            return;

        // In UI schreiben
        AppendToRichTextBox(newLines);
    }

    private bool PassesLevelFilter(string line)
    {
        var match = LevelRegex.Match(line);
        if (!match.Success)
            return true; // Unbekanntes Format durchlassen

        var level = match.Groups[1].Value;
        return _levelFilter switch
        {
            "Info" => level is "INF" or "WRN" or "ERR",
            "Warn" => level is "WRN" or "ERR",
            "Error" => level is "ERR",
            _ => true
        };
    }

    private void AppendToRichTextBox(List<(DateTime time, string line, string module)> lines)
    {
        if (_target.IsDisposed || !_target.IsHandleCreated)
            return;

        _target.BeginInvoke(() =>
        {
            _target.SuspendLayout();

            foreach (var (_, line, module) in lines)
            {
                var color = GetColorForLine(line);
                _target.SelectionStart = _target.TextLength;
                _target.SelectionLength = 0;
                _target.SelectionColor = color;
                _target.AppendText(line + Environment.NewLine);
            }

            // Bereinigung: wenn zu viele Zeilen, die ältesten entfernen
            TrimIfNeeded();

            // Auto-Scroll zum Ende
            _target.SelectionStart = _target.TextLength;
            _target.ScrollToCaret();

            _target.ResumeLayout();
        });
    }

    private static Color GetColorForLine(string line)
    {
        if (line.Contains("[ERR]"))
            return Color.Red;
        if (line.Contains("[WRN]"))
            return Color.Orange;
        if (line.Contains("[DBG]"))
            return Color.Gray;
        return Color.LightGreen; // INF
    }

    private void TrimIfNeeded()
    {
        if (_target.Lines.Length <= _maxLines)
            return;

        // Die älteste Hälfte entfernen
        var removeCount = _maxLines / 2;
        var removeEndIndex = _target.GetFirstCharIndexFromLine(removeCount);

        if (removeEndIndex <= 0)
            return;

        _target.SelectionStart = 0;
        _target.SelectionLength = removeEndIndex;
        _target.SelectedText = $"--- {removeCount} ältere Zeilen entfernt ---{Environment.NewLine}";
    }

    private void ClearAndResetPositions()
    {
        _filePositions.Clear();

        if (_target.IsHandleCreated && !_target.IsDisposed)
        {
            _target.BeginInvoke(() =>
            {
                _target.Clear();
            });
        }
    }

    public void Dispose()
    {
        _refreshTimer.Stop();
        _refreshTimer.Dispose();
    }
}
