using Microsoft.Extensions.Options;

namespace MailPrint.Services;

public class FolderWatcherService : IDisposable
{
    private readonly ILogger<FolderWatcherService> _logger;
    private readonly PrintService _printService;
    private readonly MailPrintOptions _options;
    private readonly List<FileSystemWatcher> _watchers = new();
    private readonly HashSet<string> _printed = new(StringComparer.OrdinalIgnoreCase);

    public FolderWatcherService(
        ILogger<FolderWatcherService> logger,
        PrintService printService,
        IOptions<MailPrintOptions> options)
    {
        _logger = logger;
        _printService = printService;
        _options = options.Value;
    }

    public void Start()
    {
        if (_options.FolderWatchers.Count == 0)
        {
            _logger.LogInformation("Keine Ordner-Überwachung konfiguriert.");
            return;
        }

        // Gedruckte Dateien laden (damit nach Neustart nicht nochmal gedruckt wird)
        LoadPrintedLog();

        foreach (var config in _options.FolderWatchers)
        {
            if (string.IsNullOrEmpty(config.Path) || !Directory.Exists(config.Path))
            {
                _logger.LogWarning("[{Name}] Ordner nicht gefunden: {Path}", config.Name, config.Path);
                continue;
            }

            // Beim Start bereits vorhandene PDFs drucken
            PrintExistingFiles(config);

            var watcher = new FileSystemWatcher(config.Path)
            {
                Filter = "*.pdf",
                IncludeSubdirectories = config.IncludeSubfolders,
                NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite,
                EnableRaisingEvents = true
            };

            watcher.Created += (_, e) => OnFileDetected(e.FullPath, config);
            watcher.Renamed += (_, e) =>
            {
                if (e.Name?.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase) == true)
                    OnFileDetected(e.FullPath, config);
            };

            _watchers.Add(watcher);
            _logger.LogInformation("[{Name}] Überwache: {Path} (Unterordner: {Sub})",
                config.Name, config.Path, config.IncludeSubfolders);
        }
    }

    private void PrintExistingFiles(FolderWatcher config)
    {
        var files = Directory.GetFiles(config.Path, "*.pdf",
            config.IncludeSubfolders ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly);

        if (files.Length == 0) return;

        _logger.LogInformation("[{Name}] {Count} vorhandene PDF(s) gefunden – werden gedruckt.", config.Name, files.Length);
        foreach (var file in files)
            OnFileDetected(file, config);
    }

    private void OnFileDetected(string filePath, FolderWatcher config)
    {
        // Bereits gedruckte Dateien überspringen (nur relevant wenn DeleteAfterPrint=false)
        if (_printed.Contains(filePath))
        {
            _logger.LogDebug("[{Name}] Bereits gedruckt, überspringe: {File}", config.Name, filePath);
            return;
        }

        // Kurz warten bis Datei vollständig geschrieben ist
        if (!WaitForFile(filePath))
        {
            _logger.LogWarning("[{Name}] Datei nicht lesbar (gesperrt?): {File}", config.Name, filePath);
            return;
        }

        _logger.LogInformation("[{Name}] Neue Datei: {File}", config.Name, filePath);

        try
        {
            _printService.PrintPdf(new PrintJob
            {
                FilePath = filePath,
                MailSubject = Path.GetFileName(filePath),
                MailFrom = $"Ordner:{config.Name}",
                PrinterProfileName = config.PrinterProfileName
            });

            if (config.DeleteAfterPrint)
            {
                try
                {
                    File.Delete(filePath);
                    _logger.LogInformation("[{Name}] Gelöscht: {File}", config.Name, filePath);
                }
                catch (Exception ex)
                {
                    _logger.LogWarning(ex, "[{Name}] Löschen fehlgeschlagen: {File}", config.Name, filePath);
                }
            }
            else
            {
                // Datei bleibt – merken damit sie nicht erneut gedruckt wird
                _printed.Add(filePath);
                SavePrintedLog();
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "[{Name}] Druckfehler: {File}", config.Name, filePath);
        }
    }

    private static bool WaitForFile(string path, int timeoutMs = 5000)
    {
        var sw = System.Diagnostics.Stopwatch.StartNew();
        while (sw.ElapsedMilliseconds < timeoutMs)
        {
            try
            {
                using var fs = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.None);
                return true;
            }
            catch (IOException)
            {
                Thread.Sleep(200);
            }
        }
        return false;
    }

    private static string PrintedLogPath =>
        Path.Combine(AppContext.BaseDirectory, "folder_printed.log");

    private void LoadPrintedLog()
    {
        if (!File.Exists(PrintedLogPath)) return;
        foreach (var line in File.ReadAllLines(PrintedLogPath))
            if (!string.IsNullOrWhiteSpace(line)) _printed.Add(line.Trim());
        _logger.LogInformation("Gedruckte-Dateien-Liste geladen: {Count} Einträge", _printed.Count);
    }

    private void SavePrintedLog()
    {
        try { File.WriteAllLines(PrintedLogPath, _printed); }
        catch (Exception ex) { _logger.LogWarning(ex, "Gedruckte-Dateien-Liste konnte nicht gespeichert werden"); }
    }

    public void Dispose()
    {
        foreach (var w in _watchers)
        {
            w.EnableRaisingEvents = false;
            w.Dispose();
        }
        _watchers.Clear();
    }
}
