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 HashSet<string> _lastDiscoveredPrinterIps = new();
    private List<DiscoveredPrinter> _lastDiscoveredPrinters = 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);
    }

    /// <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();

        // Scan once for now, then exit
        // By Reini, because we are loosing connection on frequent scans
        return;

        // 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;
        HashSet<string> currentIps;
        bool configChanged;

        await _printServer.EnterDiscoveryLockAsync();
        try
        {
            printers = await _discoveryService.DiscoverPrintersAsync(_discoveryTimeout);
            currentIps = printers.Select(p => p.IPAddress).ToHashSet();
            configChanged = HasConfigurationChanged(currentIps);

            if (configChanged)
            {
                _lastDiscoveredPrinterIps = currentIps;
                _lastDiscoveredPrinters = printers;
            }
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to discover printers");
            return; // Exit early on error
        }
        finally
        {
            _printServer.ExitDiscoveryLock();
        }

        // Register printers AFTER releasing the lock
        foreach (string currentIp in currentIps)
        {
            _logger.LogDebug("Registering discovered printer with IP: {IP}", currentIp);
            _printServer.RegisterPrinter(currentIp);
        }

        if (configChanged)
        {
            _logger.LogInformation("Discovered printer configuration changed: {Count} printer(s)", printers.Count);
            await _receiver.OnPrintersDiscoveredAsync(printers.AsReadOnly());
        }
    }

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

        return !currentIps.SetEquals(_lastDiscoveredPrinterIps);
    }
}
