using Microsoft.Extensions.Logging;
using Inspectron.Epson.PrintServer.Printers;
using Inspectron.Epson.PrintServer.Printers.Utils;
using Inspectron.Epson.Queue;

namespace Inspectron.Epson.PrintServer.PrintServices;

public class EpsonPrintService: IPrintService
{
    private readonly ILogger _logger;
    private readonly IPrinterFactory _printerFactory;
    private readonly IWrapperPrinterFactory _wrapperPrinterFactory;
    private readonly IPrinterConfigurationSource _printerConfigurationSource;
    private readonly IReceiptConverterFactory _receiptConverterFactory;
    private readonly HttpClient _httpClient;

    public EpsonPrintService(ILogger logger, IPrinterFactory printerFactory, IWrapperPrinterFactory wrapperPrinterFactory, IPrinterConfigurationSource printerConfigurationSource, IReceiptConverterFactory receiptConverterFactory, HttpClient httpClient)
    {
        _logger = logger;
        _printerFactory = printerFactory;
        _wrapperPrinterFactory = wrapperPrinterFactory;
        _printerConfigurationSource = printerConfigurationSource;
        _receiptConverterFactory = receiptConverterFactory;
        _httpClient = httpClient;
    }

    public async Task<PrintResult> PrintAsync(string printerIp, PrintJob job)
    {
        try
        {
            
            await using var epsonPrinter = _printerFactory.CreatePrinter();
            string address = printerIp;
            int port = 9100;

            await epsonPrinter.ConnectAsync(address, port);

            var printerId = await epsonPrinter.GetPrinterIdAsync();

            _logger.LogInformation("Printer ID({ip}): {PrinterId}", printerIp, printerId);

            var status = await epsonPrinter.GetPrinterStatusAsync();
            if (!status.IsOnline)
            {
                throw new InvalidOperationException("Printer is offline or out of paper.");
            }
            var printerAdapter = _wrapperPrinterFactory.CreatePrinterFromId(printerId.Value, epsonPrinter);

            await printerAdapter.InitAsync();

            if (!string.IsNullOrEmpty(job.Document.LogoUrl))
            {
                // Download logo image
                var logoFileName = Path.GetFileName(new Uri(job.Document.LogoUrl).LocalPath);
                var ext = Path.GetExtension(logoFileName).ToLowerInvariant();
                var encodedName = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(logoFileName));
                logoFileName = $"{encodedName}{ext}";
                var logoCachePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logos");

                // Ensure logos cache directory exists
                if (!Directory.Exists(logoCachePath))
                {
                    Directory.CreateDirectory(logoCachePath);
                }

                var cachedLogoPath = Path.Combine(logoCachePath, logoFileName);

                // Download only if not already cached
                if (!File.Exists(cachedLogoPath))
                {
                    var logoData = await _httpClient.GetByteArrayAsync(job.Document.LogoUrl);
                    await File.WriteAllBytesAsync(cachedLogoPath, logoData);

                    _logger.LogInformation("Downloaded logo {LogoFileName} from {LogoUrl}", logoFileName, job.Document.LogoUrl);
                }
                else
                {
                    _logger.LogDebug("Using cached logo {LogoFileName}", logoFileName);
                }

                await printerAdapter.PrintImageAsync(cachedLogoPath);
            }

            

            // Convert receipt content to print commands using the factory
            List<PrintCommand> commands;
            try
            {
                var converter = _receiptConverterFactory.Create(job.Document.ReceiptType, printerId.Value);
                commands = converter.Convert(job.Document.Content);
            }
            catch (Exception e)
            {
                _logger.LogWarning(e, "Conversion error for print job on printer {PrinterUid}", printerIp);
                return PrintResult.Fail(PrintErrorType.ConversionError);
            }

            await printerAdapter.PrintAsync(commands);
            await Task.Delay(200);
            status = await epsonPrinter.GetPrinterStatusAsync();
            if (!status.IsOnline)
            {
                throw new InvalidOperationException("Printer is offline or out of paper.");
            }

            await printerAdapter.Cut();
        }
        catch (Exception e)
        {
            _logger.LogWarning(e, "Failed to print job for printer {PrinterUid}", printerIp);
            return PrintResult.Fail(PrintErrorType.Other);
        }
        return PrintResult.Ok();
    }

}