using System.Reflection;
using DocumentOperator.Application.Common.Interfaces;
using DocumentOperator.Domain.Common.Exceptions;
using DocumentOperator.Infrastructure.Services.QrCodeProcessing;
using FluentAssertions;

namespace DocumentOperator.Tests.Unit.Infrastructure.Services.QrCodeProcessing;

/// <summary>
/// Unit tests for DevExpressSwissQrCodeProcessor.
/// Tests Swiss QR Code extraction and parsing from PDFs.
/// </summary>
public class DevExpressSwissQrCodeProcessorTests
{
    private readonly ISwissQrCodeProcessor _sut; // SUT = System Under Test

    public DevExpressSwissQrCodeProcessorTests()
    {
        // Arrange: Create instance
        _sut = new DevExpressSwissQrCodeProcessor();
    }

    #region Helper Methods

    /// <summary>
    /// Loads a test PDF from embedded resources as a Stream.
    /// </summary>
    /// <param name="filename">Name of the PDF file (e.g., "pdfWithSwissQRCode.pdf")</param>
    /// <returns>PDF content as MemoryStream</returns>
    private static Stream LoadTestPdf(string filename)
    {
        var assembly = Assembly.GetExecutingAssembly();
        var resourceName = $"DocumentOperator.Tests.TestData.Pdfs.{filename}";

        var stream = assembly.GetManifestResourceStream(resourceName);

        if (stream == null)
        {
            throw new FileNotFoundException(
                $"Embedded resource '{resourceName}' not found. " +
                $"Available resources: {string.Join(", ", assembly.GetManifestResourceNames())}");
        }

        // Copy to MemoryStream so caller can reuse/seek
        var memoryStream = new MemoryStream();
        stream.CopyTo(memoryStream);
        memoryStream.Position = 0; // Reset position for reading
        return memoryStream;
    }

    #endregion

    #region ExtractSwissQrCodeAsync Tests

    [Fact(Skip = "Current test PDF uses QR format not compatible with ZXing library. Requires real Swiss QR Bill PDF from https://www.swiss-qr-invoice.org/")]
    public async Task ExtractSwissQrCodeAsync_PdfWithSwissQrCode_ReturnsQrCodeData()
    {
        // Arrange
        using var pdfStream = LoadTestPdf("pdfWithSwissQRCode.pdf");

        // Act
        var (bill, rawLines) = await _sut.ExtractSwissQrCodeAsync(pdfStream);

        // Assert
        bill.Should().NotBeNull("PDF contains a Swiss QR Code");
        bill.Account.Should().NotBeNullOrEmpty("QR Code should contain IBAN");
        bill.Currency.Should().NotBeNullOrEmpty("QR Code should contain currency");
        bill.Creditor.Should().NotBeNull("QR Code should contain creditor");
        bill.Creditor.Name.Should().NotBeNullOrEmpty("QR Code should contain creditor name");
        rawLines.Should().NotBeEmpty("Raw lines should contain parsed QR code text");
    }

    [Fact(Skip = "Test PDF incompatible with ZXing - requires real Swiss QR Bill PDF")]
    public async Task ExtractSwissQrCodeAsync_PdfWithSwissQrCode_ReturnsValidIban()
    {
        // Arrange
        using var pdfStream = LoadTestPdf("pdfWithSwissQRCode.pdf");

        // Act
        var (bill, _) = await _sut.ExtractSwissQrCodeAsync(pdfStream);

        // Assert
        bill.Account.Should().MatchRegex(@"^CH\d{2}[A-Z0-9]{17}$", 
            "Swiss IBAN should match pattern: CH + 2 digits + 17 chars");
    }

    [Fact(Skip = "Test PDF incompatible with ZXing - requires real Swiss QR Bill PDF")]
    public async Task ExtractSwissQrCodeAsync_PdfWithSwissQrCode_ReturnsCurrency()
    {
        // Arrange
        using var pdfStream = LoadTestPdf("pdfWithSwissQRCode.pdf");

        // Act
        var (bill, _) = await _sut.ExtractSwissQrCodeAsync(pdfStream);

        // Assert
        bill.Currency.Should().BeOneOf("CHF", "EUR", 
            "Swiss QR Bill supports CHF and EUR currencies");
    }

    [Fact]
    public async Task ExtractSwissQrCodeAsync_PdfWithoutQrCode_ThrowsSwissQrCodeNotFoundException()
    {
        // Arrange: valid.pdf doesn't contain a Swiss QR Code
        using var pdfStream = LoadTestPdf("valid.pdf");

        // Act & Assert
        var act = async () => await _sut.ExtractSwissQrCodeAsync(pdfStream);

        await act.Should().ThrowAsync<NotFoundException>()
            .WithMessage("*No valid Swiss QR Code found*");
    }

    [Fact]
    public async Task ExtractSwissQrCodeAsync_EmptyPdf_ThrowsException()
    {
        // Arrange: Empty stream
        using var emptyStream = new MemoryStream();

        // Act & Assert
        var act = async () => await _sut.ExtractSwissQrCodeAsync(emptyStream);

        await act.Should().ThrowAsync<Exception>()
            .Where(ex => ex is ArgumentException);
    }

    [Fact]
    public async Task ExtractSwissQrCodeAsync_NullInput_ThrowsArgumentNullException()
    {
        // Arrange
        Stream nullStream = null!;

        // Act & Assert
        var act = async () => await _sut.ExtractSwissQrCodeAsync(nullStream);

        await act.Should().ThrowAsync<ArgumentNullException>();
    }

    [Fact]
    public async Task ExtractSwissQrCodeAsync_InvalidPdfBytes_ThrowsPdfProcessingException()
    {
        // Arrange: Random bytes that are not a valid PDF
        using var invalidStream = new MemoryStream("This is not a PDF file"u8.ToArray());

        // Act & Assert
        var act = async () => await _sut.ExtractSwissQrCodeAsync(invalidStream);

        await act.Should().ThrowAsync<ArgumentException>();
    }

    [Fact]
    public async Task ExtractSwissQrCodeAsync_StreamNotAtBeginning_ThrowsBadRequestException()
    {
        // Arrange: Valid PDF but stream position is not at 0
        using var pdfStream = LoadTestPdf("valid.pdf");
        pdfStream.Position = 10; // Move position away from beginning

        // Act & Assert
        var act = async () => await _sut.ExtractSwissQrCodeAsync(pdfStream);

        await act.Should().ThrowAsync<BadRequestException>();
    }

    #endregion

    #region Swiss QR Code Content Validation Tests

    [Fact(Skip = "Test PDF incompatible with ZXing - requires real Swiss QR Bill PDF")]
    public async Task ExtractSwissQrCodeAsync_ValidQrCode_ParsesCreditorInformation()
    {
        // Arrange
        using var pdfStream = LoadTestPdf("pdfWithSwissQRCode.pdf");

        // Act
        var (bill, _) = await _sut.ExtractSwissQrCodeAsync(pdfStream);

        // Assert
        bill.Creditor.Should().NotBeNull("Creditor information is required");
        bill.Creditor.Name.Should().NotBeNullOrEmpty("Creditor name is required");
        // Swiss QR Bill allows optional address fields
    }

    [Fact(Skip = "Test PDF incompatible with ZXing - requires real Swiss QR Bill PDF")]
    public async Task ExtractSwissQrCodeAsync_ValidQrCode_ParsesDebtorInformationIfPresent()
    {
        // Arrange
        using var pdfStream = LoadTestPdf("pdfWithSwissQRCode.pdf");

        // Act
        var (bill, _) = await _sut.ExtractSwissQrCodeAsync(pdfStream);

        // Assert
        // Debtor information is OPTIONAL in Swiss QR Bill Standard 2.0
        // So we just check that the property exists (can be null)
        bill.Should().NotBeNull();
        // If debtor is present, it should have a name
        if (bill.Debtor != null)
        {
            bill.Debtor.Name.Should().NotBeNullOrWhiteSpace();
        }
    }

    [Fact(Skip = "Test PDF incompatible with ZXing - requires real Swiss QR Bill PDF")]
    public async Task ExtractSwissQrCodeAsync_ValidQrCode_ParsesAmountIfPresent()
    {
        // Arrange
        using var pdfStream = LoadTestPdf("pdfWithSwissQRCode.pdf");

        // Act
        var (bill, _) = await _sut.ExtractSwissQrCodeAsync(pdfStream);

        // Assert
        // Amount is OPTIONAL in Swiss QR Bill (can be 0.00 or null for payment slips)
        if (bill.Amount.HasValue)
        {
            bill.Amount.Value.Should().BeGreaterOrEqualTo(0.01m, 
                "If amount is specified, it should be >= 0.01");
        }
    }

    #endregion
}

