using AutoFixture;
using AutoFixture.AutoMoq;
using AwesomeAssertions;
using Books.Api.EventFlow.ReadModels;
using Books.Api.EventFlow.Repositories;
using Books.Api.Reporting;
using Ledger.Core.Models;
using Ledger.Core.Services;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;

namespace Books.Api.Tests.Reporting;

/// <summary>
/// Unit tests for VatReportService.
/// Tests VAT report generation from ledger data for SKAT compliance.
/// </summary>
[Trait("Category", "Unit")]
public class VatReportServiceTests
{
    private readonly IFixture _fixture;
    private readonly Mock<IAccountRepository> _accountRepository;
    private readonly Mock<ILedgerService> _ledgerService;
    private readonly VatReportService _sut;

    private const string CompanyId = "company-123";
    private const string InputVatAccountNumber = "5610";
    private const string OutputVatAccountNumber = "5611";

    public VatReportServiceTests()
    {
        _fixture = new Fixture().Customize(new AutoMoqCustomization());
        _accountRepository = new Mock<IAccountRepository>();
        _ledgerService = new Mock<ILedgerService>();
        _sut = new VatReportService(
            _accountRepository.Object,
            _ledgerService.Object,
            NullLogger<VatReportService>.Instance);
    }

    #region Period Validation Tests

    [Fact]
    public async Task GenerateReportAsync_PeriodEndBeforeStart_ThrowsArgumentException()
    {
        // Arrange
        var periodStart = new DateOnly(2024, 3, 1);
        var periodEnd = new DateOnly(2024, 2, 1);

        // Act
        var act = () => _sut.GenerateReportAsync(CompanyId, periodStart, periodEnd);

        // Assert
        await act.Should().ThrowAsync<ArgumentException>()
            .WithMessage("*startdato*");
    }

    [Fact]
    public async Task GenerateReportAsync_PeriodExceedsOneYear_ThrowsArgumentException()
    {
        // Arrange
        var periodStart = new DateOnly(2024, 1, 1);
        var periodEnd = new DateOnly(2025, 2, 1); // 397 days

        // Act
        var act = () => _sut.GenerateReportAsync(CompanyId, periodStart, periodEnd);

        // Assert
        await act.Should().ThrowAsync<ArgumentException>()
            .WithMessage("*366*");
    }

    [Fact]
    public async Task GenerateReportAsync_ValidPeriod_DoesNotThrow()
    {
        // Arrange
        var periodStart = new DateOnly(2024, 1, 1);
        var periodEnd = new DateOnly(2024, 12, 31); // 365 days - valid

        SetupNoVatAccounts();
        SetupEmptyLedgerResponse();

        // Act
        var result = await _sut.GenerateReportAsync(CompanyId, periodStart, periodEnd);

        // Assert - should not throw, returns empty report
        result.Should().NotBeNull();
    }

    #endregion

    #region No VAT Accounts Tests

    [Fact]
    public async Task GenerateReportAsync_NoVatAccounts_ReturnsEmptyReport()
    {
        // Arrange
        var periodStart = new DateOnly(2024, 1, 1);
        var periodEnd = new DateOnly(2024, 3, 31);

        SetupNoVatAccounts();

        // Act
        var result = await _sut.GenerateReportAsync(CompanyId, periodStart, periodEnd);

        // Assert
        result.BoxA.Should().Be(0);
        result.BoxB.Should().Be(0);
        result.BoxC.Should().Be(0);
        result.BoxD.Should().Be(0);
        result.NetVat.Should().Be(0);
        result.TransactionCount.Should().Be(0);
        result.PeriodStart.Should().Be(periodStart);
        result.PeriodEnd.Should().Be(periodEnd);
    }

    #endregion

    #region Output VAT Tests

    [Fact]
    public async Task GenerateReportAsync_OnlyOutputVat_ReturnsCorrectBoxA()
    {
        // Arrange
        var periodStart = new DateOnly(2024, 1, 1);
        var periodEnd = new DateOnly(2024, 3, 31);
        var outputGuid = Guid.NewGuid();

        SetupInputVatAccount(null);
        SetupOutputVatAccount(outputGuid, "Salgsmoms");
        SetupLedgerResponse(new AccountPeriodBalance
        {
            AccountId = outputGuid,
            TotalCredits = 25000m,
            TotalDebits = 0m,
            NetChange = -25000m,
            EntryCount = 50,
            Currency = "DKK"
        });

        // Act
        var result = await _sut.GenerateReportAsync(CompanyId, periodStart, periodEnd);

        // Assert
        result.BoxA.Should().Be(25000m);
        result.BoxB.Should().Be(0);
        result.TotalOutputVat.Should().Be(25000m);
        result.TotalInputVat.Should().Be(0);
        result.NetVat.Should().Be(25000m);
        result.TransactionCount.Should().Be(50);
    }

    #endregion

    #region Input VAT Tests

    [Fact]
    public async Task GenerateReportAsync_OnlyInputVat_ReturnsCorrectBoxB()
    {
        // Arrange
        var periodStart = new DateOnly(2024, 1, 1);
        var periodEnd = new DateOnly(2024, 3, 31);
        var inputGuid = Guid.NewGuid();

        SetupInputVatAccount(inputGuid, "Købsmoms");
        SetupOutputVatAccount(null);
        SetupLedgerResponse(new AccountPeriodBalance
        {
            AccountId = inputGuid,
            TotalDebits = 15000m,
            TotalCredits = 0m,
            NetChange = 15000m,
            EntryCount = 30,
            Currency = "DKK"
        });

        // Act
        var result = await _sut.GenerateReportAsync(CompanyId, periodStart, periodEnd);

        // Assert
        result.BoxA.Should().Be(0);
        result.BoxB.Should().Be(15000m);
        result.TotalOutputVat.Should().Be(0);
        result.TotalInputVat.Should().Be(15000m);
        result.NetVat.Should().Be(-15000m); // Negative = refund
        result.TransactionCount.Should().Be(30);
    }

    #endregion

    #region Mixed VAT Tests

    [Fact]
    public async Task GenerateReportAsync_BothInputAndOutputVat_CalculatesNetCorrectly()
    {
        // Arrange
        var periodStart = new DateOnly(2024, 1, 1);
        var periodEnd = new DateOnly(2024, 3, 31);
        var inputGuid = Guid.NewGuid();
        var outputGuid = Guid.NewGuid();

        SetupInputVatAccount(inputGuid, "Købsmoms");
        SetupOutputVatAccount(outputGuid, "Salgsmoms");
        SetupLedgerResponse(
            new AccountPeriodBalance
            {
                AccountId = inputGuid,
                TotalDebits = 10000m,
                TotalCredits = 0m,
                NetChange = 10000m,
                EntryCount = 20,
                Currency = "DKK"
            },
            new AccountPeriodBalance
            {
                AccountId = outputGuid,
                TotalCredits = 25000m,
                TotalDebits = 0m,
                NetChange = -25000m,
                EntryCount = 40,
                Currency = "DKK"
            });

        // Act
        var result = await _sut.GenerateReportAsync(CompanyId, periodStart, periodEnd);

        // Assert
        result.BoxA.Should().Be(25000m);
        result.BoxB.Should().Be(10000m);
        result.TotalOutputVat.Should().Be(25000m);
        result.TotalInputVat.Should().Be(10000m);
        result.NetVat.Should().Be(15000m); // 25000 - 10000 = to pay
        result.TransactionCount.Should().Be(60);
    }

    #endregion

    #region Basis Calculation Tests

    [Fact]
    public async Task GenerateReportAsync_WithOutputVat_CalculatesBasis1From25PercentRate()
    {
        // Arrange
        var periodStart = new DateOnly(2024, 1, 1);
        var periodEnd = new DateOnly(2024, 3, 31);
        var outputGuid = Guid.NewGuid();

        SetupInputVatAccount(null);
        SetupOutputVatAccount(outputGuid, "Salgsmoms");
        SetupLedgerResponse(new AccountPeriodBalance
        {
            AccountId = outputGuid,
            TotalCredits = 2500m, // 25% VAT on 10000
            TotalDebits = 0m,
            NetChange = -2500m,
            EntryCount = 10,
            Currency = "DKK"
        });

        // Act
        var result = await _sut.GenerateReportAsync(CompanyId, periodStart, periodEnd);

        // Assert
        result.BoxA.Should().Be(2500m);
        result.Basis1.Should().Be(10000m); // 2500 / 0.25 = 10000
    }

    #endregion

    #region Empty Period Tests

    [Fact]
    public async Task GenerateReportAsync_EmptyPeriod_ReturnsZeroAmounts()
    {
        // Arrange
        var periodStart = new DateOnly(2024, 1, 1);
        var periodEnd = new DateOnly(2024, 3, 31);
        var inputGuid = Guid.NewGuid();
        var outputGuid = Guid.NewGuid();

        SetupInputVatAccount(inputGuid, "Købsmoms");
        SetupOutputVatAccount(outputGuid, "Salgsmoms");
        SetupEmptyLedgerResponse();

        // Act
        var result = await _sut.GenerateReportAsync(CompanyId, periodStart, periodEnd);

        // Assert
        result.BoxA.Should().Be(0);
        result.BoxB.Should().Be(0);
        result.TransactionCount.Should().Be(0);
        result.NetVat.Should().Be(0);
    }

    #endregion

    #region Helper Methods

    private void SetupNoVatAccounts()
    {
        _accountRepository
            .Setup(x => x.GetByCompanyAndNumberAsync(
                It.IsAny<string>(),
                It.IsAny<string>(),
                It.IsAny<CancellationToken>()))
            .ReturnsAsync((AccountReadModelDto?)null);
    }

    private void SetupEmptyLedgerResponse()
    {
        _ledgerService
            .Setup(x => x.QueryEntriesAsync(
                It.IsAny<EntriesQuery>(),
                It.IsAny<CancellationToken>()))
            .ReturnsAsync(new EntriesQueryResult { Aggregates = [] });
    }

    private void SetupLedgerResponse(params AccountPeriodBalance[] balances)
    {
        _ledgerService
            .Setup(x => x.QueryEntriesAsync(
                It.IsAny<EntriesQuery>(),
                It.IsAny<CancellationToken>()))
            .ReturnsAsync(new EntriesQueryResult { Aggregates = balances.ToList() });
    }

    private void SetupInputVatAccount(Guid? accountGuid, string name = "Købsmoms")
    {
        var account = accountGuid.HasValue
            ? new AccountReadModelDto
            {
                Id = $"account-{accountGuid.Value}",
                CompanyId = CompanyId,
                AccountNumber = InputVatAccountNumber,
                Name = name
            }
            : null;

        _accountRepository
            .Setup(x => x.GetByCompanyAndNumberAsync(
                CompanyId,
                InputVatAccountNumber,
                It.IsAny<CancellationToken>()))
            .ReturnsAsync(account);
    }

    private void SetupOutputVatAccount(Guid? accountGuid, string name = "Salgsmoms")
    {
        var account = accountGuid.HasValue
            ? new AccountReadModelDto
            {
                Id = $"account-{accountGuid.Value}",
                CompanyId = CompanyId,
                AccountNumber = OutputVatAccountNumber,
                Name = name
            }
            : null;

        _accountRepository
            .Setup(x => x.GetByCompanyAndNumberAsync(
                CompanyId,
                OutputVatAccountNumber,
                It.IsAny<CancellationToken>()))
            .ReturnsAsync(account);
    }

    #endregion
}
