using Codecrete.SwissQRBill.Generator;
using DevExpress.Drawing;
using DevExpress.Pdf;
using DocumentOperator.Application.Common.Interfaces;
using DocumentOperator.Domain.Exceptions;
using DocumentOperator.Domain.ValueObjects;
using System.Drawing;
using System.Runtime.Versioning;
using ZXing;

namespace DocumentOperator.Infrastructure.Services.QrCodeProcessing;

/// <summary>
/// Swiss QR Code processor using DevExpress PDF API for PDF access
/// and Codecrete.SwissQRBill.Generator for QR Code parsing.
/// </summary>
public sealed class DevExpressSwissQrCodeProcessor : ISwissQrCodeProcessor
{
    private const int QrCodeSearchDpi = 300; // High DPI for better QR code recognition

    /// <inheritdoc />
    [SupportedOSPlatform("windows")]
    public async Task<SwissQrCodeData> ExtractSwissQrCodeAsync(byte[] pdfBytes, CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(pdfBytes);

        try
        {
            using var pdfDocument = new PdfDocumentProcessor();
            pdfDocument.LoadDocument(new MemoryStream(pdfBytes));

            if (pdfDocument.Document.Pages.Count == 0)
            {
                throw new ArgumentException("PDF document contains no pages.", nameof(pdfBytes));
            }

            // Get last page
            int lastPageIndex = pdfDocument.Document.Pages.Count - 1;

            // Convert last page to image for QR code detection
            using var pageImage = RenderPageToImage(pdfDocument, lastPageIndex);

            // Detect and decode QR code
            string? qrCodeContent = DecodeQrCodeFromImage(pageImage);

            if (string.IsNullOrEmpty(qrCodeContent))
            {
                throw new SwissQrCodeNotFoundException(
                    $"No QR Code found on the last page (page {lastPageIndex + 1}) of the PDF document.");
            }

            // Parse Swiss QR Bill content using Codecrete library
            SwissQrCodeData qrCodeData = ParseSwissQrBillContent(qrCodeContent);

            return await Task.FromResult(qrCodeData);
        }
        catch (SwissQrCodeNotFoundException)
        {
            throw;
        }
        catch (Exception ex)
        {
            throw new ArgumentException("Failed to extract Swiss QR Code from PDF.", nameof(pdfBytes), ex);
        }
    }

    /// <summary>
    /// Renders a PDF page to a high-resolution bitmap for QR code detection
    /// </summary>
    private static DXBitmap RenderPageToImage(PdfDocumentProcessor processor, int pageIndex)
    {
        // Render page at high DPI for better QR code recognition
        var pageImage = processor.CreateDXBitmap(pageIndex + 1, QrCodeSearchDpi);
        return pageImage;
    }

    /// <summary>
    /// Decodes QR code from a DXBitmap image using ZXing library
    /// </summary>
    [SupportedOSPlatform("windows")]
    private static string? DecodeQrCodeFromImage(DXBitmap dxImage)
    {
        // Convert DXBitmap to System.Drawing.Bitmap via MemoryStream
        using var ms = new MemoryStream();
        dxImage.Save(ms, DXImageFormat.Png);
        ms.Position = 0;

        using var gdiImage = Image.FromStream(ms);
        using var gdiBitmap = new Bitmap(gdiImage);

        var reader = new ZXing.Windows.Compatibility.BarcodeReader
        {
            AutoRotate = true,
            Options = new ZXing.Common.DecodingOptions
            {
                PossibleFormats = [BarcodeFormat.QR_CODE],
                TryHarder = true,
                TryInverted = true
            }
        };

        var result = reader.Decode(gdiBitmap);
        return result?.Text;
    }

    /// <summary>
    /// Parses Swiss QR Bill content using Codecrete library
    /// </summary>
    private static SwissQrCodeData ParseSwissQrBillContent(string qrCodeText)
    {
        try
        {
            // Decode Swiss QR Bill using Codecrete library
            var bill = QRBill.DecodeQrCodeText(qrCodeText);

            // Determine reference type based on presence and format of reference
            string referenceType = DetermineReferenceType(bill.Reference);

            // Map AlternativeSchemes to string array
            var alternativeParams = bill.AlternativeSchemes?
                .Select(s => $"{s.Name}: {s.Instruction}")
                .ToArray();

            // Map to our domain value object
            return new SwissQrCodeData
            {
                QrType = "SPC", // Always SPC for Swiss Payment Code
                Version = bill.Version.ToString("D4"), // e.g., "0200" for version 2.0
                CodingType = "1", // Always UTF-8
                Iban = bill.Account ?? string.Empty,
                Creditor = MapAddress(bill.Creditor),
                UltimateCreditor = null, // Not exposed in Codecrete Bill model
                Amount = bill.Amount,
                Currency = bill.Currency ?? "CHF",
                UltimateDebtor = bill.Debtor != null ? MapAddress(bill.Debtor) : null,
                ReferenceType = referenceType,
                Reference = bill.Reference,
                UnstructuredMessage = bill.UnstructuredMessage,
                BillInformation = bill.BillInformation,
                AlternativeProcedureParameters = alternativeParams
            };
        }
        catch (Exception ex)
        {
            throw new ArgumentException(
                "Failed to parse Swiss QR Code content. The QR code may not be a valid Swiss QR Bill.", "qrCodeText", ex);
        }
    }

    /// <summary>
    /// Determines reference type based on reference string format
    /// </summary>
    private static string DetermineReferenceType(string? reference)
    {
        if (string.IsNullOrWhiteSpace(reference))
            return "NON";

        // QRR (QR Reference): 27 digits
        if (reference.Length == 27 && reference.All(char.IsDigit))
            return "QRR";

        // SCOR (Creditor Reference ISO 11649): starts with RF and has check digits
        if (reference.StartsWith("RF", StringComparison.OrdinalIgnoreCase) && reference.Length >= 5)
            return "SCOR";

        return "NON";
    }

    /// <summary>
    /// Maps Codecrete Address to our AddressData value object.
    /// 
    /// NOTE: AddressLine1 and AddressLine2 (Combined Address / K-Type) are deprecated 
    /// as of Swiss Payment Standards 2025 (effective 21 Nov 2025). 
    /// The Swiss QR Bill now mandates Structured Address (S-Type) format.
    /// These fields are retained for backward compatibility with legacy QR codes 
    /// generated before the deprecation date.
    /// </summary>
    private static AddressData MapAddress(Codecrete.SwissQRBill.Generator.Address address)
    {
        return new AddressData
        {
            AddressType = address.Type == Codecrete.SwissQRBill.Generator.Address.AddressType.Structured ? "S" : "K",
            Name = address.Name ?? string.Empty,
            Street = address.Street,
            BuildingNumber = address.HouseNo,
#pragma warning disable CS0618 // AddressLine1/AddressLine2 obsolete but required for backward compatibility
            AddressLine1 = address.AddressLine1,
            AddressLine2 = address.AddressLine2,
#pragma warning restore CS0618
            PostalCode = address.PostalCode ?? string.Empty,
            City = address.Town ?? string.Empty,
            Country = address.CountryCode ?? string.Empty
        };
    }
}
