using Inspectron.Epson;
using Inspectron.Epson.PrintServer.ConfigurationSources;
using Inspectron.Epson.Queue;
using Microsoft.Extensions.Logging;

namespace EpsonPrintService;

/// <summary>
/// Background task that periodically discovers printers and notifies receivers when changes occur
/// </summary>
public class PrinterDiscoveryBackgroundTask
{
    private readonly IDiscoveryService _discoveryService;
    private readonly IDiscoveredPrintersReceiver _receiver;
    private readonly ILogger _logger;
    private readonly PrintServer _printServer;
    private readonly TimeSpan _interval;
    private readonly TimeSpan _discoveryTimeout;
    private readonly TimeSpan _retentionPeriod;

    private readonly Dictionary<string, DateTime> _lastSeenTimes = new();
    private readonly Dictionary<string, DiscoveredPrinter> _knownPrinters = new();
    private HashSet<string> _lastEffectiveIps = new();

    public PrinterDiscoveryBackgroundTask(
        IDiscoveryService discoveryService,
        IDiscoveredPrintersReceiver receiver,
        ILogger logger,
        PrintServer printServer)
    {
        _discoveryService = discoveryService;
        _receiver = receiver;
        _logger = logger;
        _printServer = printServer;
        _interval = TimeSpan.FromSeconds(30);
        _discoveryTimeout = TimeSpan.FromSeconds(3);
        _retentionPeriod = TimeSpan.FromMinutes(2);
    }

    /// <summary>
    /// Starts the background discovery task
    /// </summary>
    /// <param name="cancellationToken">Token to cancel the background task</param>
    public async Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Starting printer discovery background task (interval: {Interval}s)", _interval.TotalSeconds);

        // Run immediately on start
        await DiscoverAndNotifyAsync();

        // Then run periodically
        while (!cancellationToken.IsCancellationRequested)
        {
            try
            {
                await Task.Delay(_interval, cancellationToken);
                await DiscoverAndNotifyAsync();
            }
            catch (TaskCanceledException)
            {
                break;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error during printer discovery");
            }
        }

        _logger.LogInformation("Printer discovery background task stopped");
    }

    private async Task DiscoverAndNotifyAsync()
    {
        List<DiscoveredPrinter> printers;

        await _printServer.EnterDiscoveryLockAsync();
        try
        {
            printers = await _discoveryService.DiscoverPrintersAsync(_discoveryTimeout);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to discover printers");
            return; // Exit early on error
        }
        finally
        {
            _printServer.ExitDiscoveryLock();
        }

        // Update last seen times for discovered printers
        DateTime now = DateTime.UtcNow;
        foreach (DiscoveredPrinter printer in printers)
        {
            _lastSeenTimes[printer.IPAddress] = now;
            _knownPrinters[printer.IPAddress] = printer;
        }

        // Remove printers not seen within the retention period
        List<string> expiredIps = _lastSeenTimes
            .Where(kvp => now - kvp.Value > _retentionPeriod)
            .Select(kvp => kvp.Key)
            .ToList();

        foreach (string expiredIp in expiredIps)
        {
            _logger.LogInformation("Printer {IP} not seen for over {Retention}s, removing", expiredIp, _retentionPeriod.TotalSeconds);
            _lastSeenTimes.Remove(expiredIp);
            _knownPrinters.Remove(expiredIp);
        }

        // Compute effective set and detect changes
        HashSet<string> effectiveIps = _lastSeenTimes.Keys.ToHashSet();
        bool configChanged = HasConfigurationChanged(effectiveIps);

        // Register newly appeared printers
        foreach (string ip in effectiveIps.Except(_lastEffectiveIps))
        {
            _logger.LogDebug("Registering discovered printer with IP: {IP}", ip);
            _printServer.RegisterPrinter(ip);
        }

        // Unregister expired printers
        foreach (string ip in _lastEffectiveIps.Except(effectiveIps))
        {
            _logger.LogDebug("Unregistering expired printer with IP: {IP}", ip);
            _printServer.UnregisterPrinter(ip);
        }

        if (configChanged)
        {
            _lastEffectiveIps = effectiveIps;
            List<DiscoveredPrinter> effectivePrinters = _knownPrinters.Values.ToList();
            _logger.LogInformation("Discovered printer configuration changed: {Count} printer(s)", effectivePrinters.Count);
            await _receiver.OnPrintersDiscoveredAsync(effectivePrinters.AsReadOnly());
        }
    }

    private bool HasConfigurationChanged(HashSet<string> effectiveIps)
    {
        if (effectiveIps.Count != _lastEffectiveIps.Count)
            return true;

        return !effectiveIps.SetEquals(_lastEffectiveIps);
    }
}
