using System.Collections.Concurrent;
using XYZ.PrintService.Models;
using XYZ.PrintService.Native;

namespace XYZ.PrintService.Services;

public static class PrintJobQueueService
{
    private static readonly ConcurrentQueue<PrintJob> _pendingJobs = new();
    private static readonly ConcurrentDictionary<Guid, PrintJob> _jobs = new();
    private static readonly object _startLock = new();
    private static bool _workerStarted = false;
    private const int MaxAttemptsPerItem = 3;

    public static PrintJob EnqueueBatch(string printerName, IEnumerable<PrintBatchItem> items)
    {
        if (string.IsNullOrWhiteSpace(printerName))
        {
            throw new ArgumentException("Printer name is required for batch job.", nameof(printerName));
        }

        var job = new PrintJob
        {
            JobId = Guid.NewGuid(),
            PrinterName = printerName,
            Status = PrintJobStatus.Queued,
            CreatedAt = DateTime.UtcNow,
            Items = items.Select(i => new PrintJobItem
            {
                ItemId = i.ItemId ?? string.Empty,
                Zpl = i.Zpl,
                Status = PrintJobItemStatus.Queued,
                Attempts = 0
            }).ToList()
        };

        if (job.Items.Count == 0)
        {
            throw new ArgumentException("Batch job must contain at least one item.", nameof(items));
        }

        _jobs[job.JobId] = job;
        _pendingJobs.Enqueue(job);

        EnsureWorkerStarted();

        return job;
    }

    public static PrintJob? GetJob(Guid jobId)
    {
        _jobs.TryGetValue(jobId, out var job);
        return job;
    }

    private static void EnsureWorkerStarted()
    {
        if (_workerStarted) return;

        lock (_startLock)
        {
            if (_workerStarted) return;
            _workerStarted = true;
            Task.Run(WorkerLoop);
        }
    }

    private static async Task WorkerLoop()
    {
        while (true)
        {
            if (_pendingJobs.TryDequeue(out var job))
            {
                try
                {
                    ProcessJob(job);
                }
                catch
                {
                    // If processing throws unexpectedly, mark job as failed
                    job.Status = PrintJobStatus.Failed;
                    job.CompletedAt = DateTime.UtcNow;
                }
            }
            else
            {
                // No jobs currently pending
                await Task.Delay(250);
            }
        }
    }

    private static void ProcessJob(PrintJob job)
    {
        job.Status = PrintJobStatus.Printing;

        foreach (var item in job.Items)
        {
            if (string.IsNullOrWhiteSpace(item.Zpl))
            {
                item.Status = PrintJobItemStatus.Failed;
                item.LastError = "ZPL data is empty.";
                continue;
            }

            var attempts = 0;
            var success = false;
            Exception? lastException = null;

            while (attempts < MaxAttemptsPerItem && !success)
            {
                attempts++;
                item.Attempts = attempts;

                try
                {
                    var result = RawPrinterHelper.SendStringToPrinter(job.PrinterName, item.Zpl);
                    if (!result.Success)
                    {
                        throw new Exception(result.Message);
                    }

                    item.Status = PrintJobItemStatus.Success;
                    item.LastError = null;
                    success = true;
                }
                catch (Exception ex)
                {
                    lastException = ex;
                    item.Status = PrintJobItemStatus.Failed;
                    item.LastError = ex.Message;

                    if (attempts < MaxAttemptsPerItem)
                    {
                        // Simple backoff before next attempt
                        Thread.Sleep(500 * attempts);
                    }
                }
            }

            if (!success && lastException != null)
            {
                // Final failure after retries
                item.Status = PrintJobItemStatus.Failed;
                item.LastError = lastException.Message;
            }
        }

        // Finalize job status
        var anyFailed = job.Items.Any(i => i.Status == PrintJobItemStatus.Failed);
        var anySuccess = job.Items.Any(i => i.Status == PrintJobItemStatus.Success);

        if (anySuccess && anyFailed)
        {
            job.Status = PrintJobStatus.CompletedWithErrors;
        }
        else if (anySuccess && !anyFailed)
        {
            job.Status = PrintJobStatus.Completed;
        }
        else
        {
            job.Status = PrintJobStatus.Failed;
        }

        job.CompletedAt = DateTime.UtcNow;
    }
}

