using AutoMapper;
using DocumentOperator.Application.Common.DTOs;
using DocumentOperator.Application.Common.Interfaces;
using DocumentOperator.Application.ValidatePdfA.Queries;
using DocumentOperator.Domain.Common.Exceptions;

using FluentAssertions;
using Moq;
using Xunit;

namespace DocumentOperator.Tests.Unit.Application.Features.ValidatePdfA;

public class ValidatePdfAQueryHandlerTests
{
    private readonly Mock<IPdfProcessor> _mockPdfProcessor;
    private readonly Mock<IMapper> _mockMapper;
    private readonly ValidatePdfAQueryHandler _handler;

    public ValidatePdfAQueryHandlerTests()
    {
        _mockPdfProcessor = new Mock<IPdfProcessor>();
        _mockMapper = new Mock<IMapper>();
        _handler = new ValidatePdfAQueryHandler(_mockPdfProcessor.Object, _mockMapper.Object);
    }

    [Fact]
    public async Task Handle_ValidPdfA_ReturnsPdfAMetadata()
    {
        // Arrange
        var pdfBytes = "%PDF"u8.ToArray(); // "%PDF"
        var query = new ValidatePdfAQuery { PdfStream = new MemoryStream(pdfBytes) };

        var domainMetadata = new PdfAMetadata(
            isValid: true,
            pdfVersion: "1.7",
            pageCount: 3,
            fileSizeBytes: 2048,
            encrypted: false,
            pdfaVersion: "PDF/A-3b",
            pdfaCompliant: true,
            errors: [],
            warnings: []
        );

        var expectedDto = new PdfAValidationResult
        {
            IsValid = true,
            PdfVersion = "1.7",
            PageCount = 3,
            FileSize = 2048,
            Encrypted = false,
            PdfAVersion = "PDF/A-3b",
            PdfACompliant = true,
            Errors = new List<string>(),
            Warnings = new List<string>()
        };

        _mockPdfProcessor
            .Setup(x => x.ValidatePdfAAsync(It.IsAny<Stream>()))
            .ReturnsAsync(domainMetadata);

        _mockMapper
            .Setup(x => x.Map<PdfAValidationResult>(domainMetadata))
            .Returns(expectedDto);

        // Act
        var result = await _handler.Handle(query, CancellationToken.None);

        // Assert
        result.Should().NotBeNull();
        result.IsValid.Should().BeTrue();
        result.PageCount.Should().Be(3);
        result.PdfVersion.Should().Be("1.7");
        result.PdfAVersion.Should().Be("PDF/A-3b");
        result.PdfACompliant.Should().BeTrue();
        result.Encrypted.Should().BeFalse();
        result.Errors.Should().BeEmpty();
        result.Warnings.Should().BeEmpty();

        _mockPdfProcessor.Verify(x => x.ValidatePdfAAsync(It.IsAny<Stream>()), Times.Once);
        _mockMapper.Verify(x => x.Map<PdfAValidationResult>(domainMetadata), Times.Once);
    }

    [Fact]
    public async Task Handle_NonCompliantPdfA_ReturnsErrorsAndWarnings()
    {
        // Arrange
        var pdfBytes = new byte[] { 0x25, 0x50, 0x44, 0x46 }; // "%PDF"
        var query = new ValidatePdfAQuery { PdfStream = new MemoryStream(pdfBytes) };

        var errors = new List<string> { "Missing XMP metadata", "Invalid color space" };
        var warnings = new List<string> { "Embedded font not subset" };

        var domainMetadata = new PdfAMetadata(
            isValid: false,
            pdfVersion: "1.4",
            pageCount: 2,
            fileSizeBytes: 1024,
            encrypted: false,
            pdfaVersion: null,
            pdfaCompliant: false,
            errors: errors,
            warnings: warnings
        );

        var expectedDto = new PdfAValidationResult
        {
            IsValid = false,
            PdfVersion = "1.4",
            PageCount = 2,
            FileSize = 1024,
            Encrypted = false,
            PdfAVersion = null,
            PdfACompliant = false,
            Errors = errors,
            Warnings = warnings
        };

        _mockPdfProcessor
            .Setup(x => x.ValidatePdfAAsync(It.IsAny<Stream>()))
            .ReturnsAsync(domainMetadata);

        _mockMapper
            .Setup(x => x.Map<PdfAValidationResult>(domainMetadata))
            .Returns(expectedDto);

        // Act
        var result = await _handler.Handle(query, CancellationToken.None);

        // Assert
        result.Should().NotBeNull();
        result.IsValid.Should().BeFalse();
        result.PdfACompliant.Should().BeFalse();
        result.PdfAVersion.Should().BeNull();
        result.Errors.Should().HaveCount(2);
        result.Errors.Should().Contain("Missing XMP metadata");
        result.Warnings.Should().HaveCount(1);

        _mockPdfProcessor.Verify(x => x.ValidatePdfAAsync(It.IsAny<Stream>()), Times.Once);
    }

    [Fact]
    public async Task Handle_EncryptedPdf_ReturnsEncryptedFlag()
    {
        // Arrange
        var pdfBytes = new byte[] { 0x25, 0x50, 0x44, 0x46 }; // "%PDF"
        var query = new ValidatePdfAQuery { PdfStream = new MemoryStream(pdfBytes) };

        var domainMetadata = new PdfAMetadata(
            isValid: true,
            pdfVersion: "1.7",
            pageCount: 1,
            fileSizeBytes: 512,
            encrypted: true,
            pdfaVersion: null,
            pdfaCompliant: false,
            errors: new List<string> { "Encrypted PDFs cannot be PDF/A compliant" },
            warnings: new List<string>()
        );

        var expectedDto = new PdfAValidationResult
        {
            IsValid = true,
            PdfVersion = "1.7",
            PageCount = 1,
            FileSize = 512,
            Encrypted = true,
            PdfAVersion = null,
            PdfACompliant = false,
            Errors = new List<string> { "Encrypted PDFs cannot be PDF/A compliant" },
            Warnings = new List<string>()
        };

        _mockPdfProcessor
            .Setup(x => x.ValidatePdfAAsync(It.IsAny<Stream>()))
            .ReturnsAsync(domainMetadata);

        _mockMapper
            .Setup(x => x.Map<PdfAValidationResult>(domainMetadata))
            .Returns(expectedDto);

        // Act
        var result = await _handler.Handle(query, CancellationToken.None);

        // Assert
        result.Should().NotBeNull();
        result.Encrypted.Should().BeTrue();
        result.PdfACompliant.Should().BeFalse();
        result.Errors.Should().Contain("Encrypted PDFs cannot be PDF/A compliant");

        _mockPdfProcessor.Verify(x => x.ValidatePdfAAsync(It.IsAny<Stream>()), Times.Once);
    }

    [Fact]
    public async Task Handle_PdfProcessorThrowsException_PropagatesException()
    {
        // Arrange
        var pdfBytes = new byte[] { 0x25, 0x50, 0x44, 0x46 }; // "%PDF"
        var query = new ValidatePdfAQuery { PdfStream = new MemoryStream(pdfBytes) };

        _mockPdfProcessor
            .Setup(x => x.ValidatePdfAAsync(It.IsAny<Stream>()))
            .ThrowsAsync(new BadRequestException("Invalid PDF format"));

        // Act & Assert
        var exception = await Assert.ThrowsAsync<BadRequestException>(
            () => _handler.Handle(query, CancellationToken.None)
        );

        exception.Message.Should().Be("Invalid PDF format");
        _mockPdfProcessor.Verify(x => x.ValidatePdfAAsync(It.IsAny<Stream>()), Times.Once);
    }
}

