using Books.Api.Domain;
using Books.Api.Domain.FiscalYears;
using Books.Api.EventFlow.Repositories;
using EventFlow.Commands;

namespace Books.Api.Commands.FiscalYears;

/// <summary>
/// Command handler for creating a new fiscal year.
/// Validates overlap with existing fiscal years and checks for gaps.
/// </summary>
public class CreateFiscalYearCommandHandler(
    IFiscalYearRepository fiscalYearRepository)
    : CommandHandler<FiscalYearAggregate, FiscalYearId, CreateFiscalYearCommand>
{
    public override async Task ExecuteAsync(
        FiscalYearAggregate aggregate,
        CreateFiscalYearCommand command,
        CancellationToken cancellationToken)
    {
        // Check for overlapping fiscal years
        var hasOverlap = await fiscalYearRepository.HasOverlappingYearAsync(
            command.CompanyId,
            command.StartDate,
            command.EndDate,
            excludeId: null,
            cancellationToken);

        if (hasOverlap)
        {
            throw new DomainException(
                "FISCAL_YEAR_OVERLAP",
                "The fiscal year overlaps with an existing fiscal year",
                "Regnskabsåret overlapper med et eksisterende regnskabsår");
        }

        // Check for gaps: verify the new start date follows the latest existing fiscal year
        if (!command.IsFirstFiscalYear)
        {
            var existingYears = await fiscalYearRepository.GetByCompanyIdAsync(
                command.CompanyId, cancellationToken);

            if (existingYears.Count > 0)
            {
                // Find the latest end date among existing fiscal years
                var latestEndDate = existingYears
                    .Select(fy => DateOnly.FromDateTime(fy.EndDate))
                    .Max();

                var expectedStartDate = latestEndDate.AddDays(1);

                if (command.StartDate != expectedStartDate)
                {
                    throw new DomainException(
                        "FISCAL_YEAR_GAP",
                        $"Fiscal year must start on {expectedStartDate:yyyy-MM-dd} (day after previous year ends on {latestEndDate:yyyy-MM-dd}). No gaps are allowed between fiscal years.",
                        $"Regnskabsåret skal starte den {expectedStartDate:yyyy-MM-dd} (dagen efter forrige år slutter den {latestEndDate:yyyy-MM-dd}). Der må ikke være huller mellem regnskabsår.");
                }
            }
        }

        aggregate.Create(
            command.CompanyId,
            command.Name,
            command.StartDate,
            command.EndDate,
            command.IsFirstFiscalYear,
            command.IsReorganization);
    }
}

public class CloseFiscalYearCommandHandler : CommandHandler<FiscalYearAggregate, FiscalYearId, CloseFiscalYearCommand>
{
    public override Task ExecuteAsync(
        FiscalYearAggregate aggregate,
        CloseFiscalYearCommand command,
        CancellationToken cancellationToken)
    {
        aggregate.Close(command.ClosedBy);
        return Task.CompletedTask;
    }
}

public class ReopenFiscalYearCommandHandler : CommandHandler<FiscalYearAggregate, FiscalYearId, ReopenFiscalYearCommand>
{
    public override Task ExecuteAsync(
        FiscalYearAggregate aggregate,
        ReopenFiscalYearCommand command,
        CancellationToken cancellationToken)
    {
        aggregate.Reopen(command.ReopenedBy);
        return Task.CompletedTask;
    }
}

public class LockFiscalYearCommandHandler : CommandHandler<FiscalYearAggregate, FiscalYearId, LockFiscalYearCommand>
{
    public override Task ExecuteAsync(
        FiscalYearAggregate aggregate,
        LockFiscalYearCommand command,
        CancellationToken cancellationToken)
    {
        aggregate.Lock(command.LockedBy);
        return Task.CompletedTask;
    }
}

public class MarkOpeningBalancePostedCommandHandler : CommandHandler<FiscalYearAggregate, FiscalYearId, MarkOpeningBalancePostedCommand>
{
    public override Task ExecuteAsync(
        FiscalYearAggregate aggregate,
        MarkOpeningBalancePostedCommand command,
        CancellationToken cancellationToken)
    {
        aggregate.MarkOpeningBalancePosted();
        return Task.CompletedTask;
    }
}
