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

namespace Books.Api.Commands.JournalEntryDrafts;

public class CreateJournalEntryDraftCommandHandler
    : CommandHandler<JournalEntryDraftAggregate, JournalEntryDraftId, CreateJournalEntryDraftCommand>
{
    public override Task ExecuteAsync(
        JournalEntryDraftAggregate aggregate,
        CreateJournalEntryDraftCommand command,
        CancellationToken cancellationToken)
    {
        aggregate.Create(
            command.CompanyId,
            command.Name,
            command.CreatedBy,
            command.VoucherNumber,
            command.ExtractionData);

        return Task.CompletedTask;
    }
}

public class UpdateJournalEntryDraftCommandHandler
    : CommandHandler<JournalEntryDraftAggregate, JournalEntryDraftId, UpdateJournalEntryDraftCommand>
{
    public override Task ExecuteAsync(
        JournalEntryDraftAggregate aggregate,
        UpdateJournalEntryDraftCommand command,
        CancellationToken cancellationToken)
    {
        aggregate.Update(
            command.Name,
            command.DocumentDate,
            command.Description,
            command.FiscalYearId,
            command.Lines,
            command.AttachmentIds);

        return Task.CompletedTask;
    }
}

/// <summary>
/// Command handler for posting a journal entry draft.
/// Validates fiscal year status and date range before allowing the post.
/// </summary>
public class MarkJournalEntryDraftPostedCommandHandler(
    IJournalEntryDraftRepository draftRepository,
    IFiscalYearRepository fiscalYearRepository)
    : CommandHandler<JournalEntryDraftAggregate, JournalEntryDraftId, MarkJournalEntryDraftPostedCommand>
{
    public override async Task ExecuteAsync(
        JournalEntryDraftAggregate aggregate,
        MarkJournalEntryDraftPostedCommand command,
        CancellationToken cancellationToken)
    {
        // Load the draft read model to get fiscal year and document date
        var draft = await draftRepository.GetByIdAsync(
            aggregate.Id.Value, cancellationToken);

        var fiscalYearId = draft?.FiscalYearId ?? aggregate.FiscalYearId;

        // Validate fiscal year is set
        if (string.IsNullOrWhiteSpace(fiscalYearId))
        {
            throw new DomainException(
                "FISCAL_YEAR_REQUIRED",
                "Fiscal year is required for posting a journal entry",
                "Regnskabsår er påkrævet for bogføring af en postering");
        }

        // Fetch and validate fiscal year
        var fiscalYear = await fiscalYearRepository.GetByIdAsync(
            fiscalYearId, cancellationToken);

        if (fiscalYear == null)
        {
            throw new DomainException(
                "FISCAL_YEAR_NOT_FOUND",
                $"Fiscal year '{fiscalYearId}' not found",
                $"Regnskabsår '{fiscalYearId}' blev ikke fundet");
        }

        // Validate fiscal year is open (not Closed or Locked)
        if (fiscalYear.Status != "Open")
        {
            throw new DomainException(
                "FISCAL_YEAR_NOT_OPEN",
                $"Fiscal year is {fiscalYear.Status}. Only open fiscal years allow posting.",
                $"Regnskabsåret er {fiscalYear.Status}. Kun åbne regnskabsår tillader bogføring.");
        }

        // Validate document date falls within fiscal year range (if document date is set)
        if (draft?.DocumentDate != null)
        {
            var documentDate = DateOnly.FromDateTime(draft.DocumentDate.Value);
            var fyStart = DateOnly.FromDateTime(fiscalYear.StartDate);
            var fyEnd = DateOnly.FromDateTime(fiscalYear.EndDate);

            if (documentDate < fyStart || documentDate > fyEnd)
            {
                throw new DomainException(
                    "DOCUMENT_DATE_OUTSIDE_FISCAL_YEAR",
                    $"Document date {documentDate:yyyy-MM-dd} falls outside the fiscal year ({fyStart:yyyy-MM-dd} to {fyEnd:yyyy-MM-dd})",
                    $"Bilagsdato {documentDate:yyyy-MM-dd} ligger uden for regnskabsåret ({fyStart:yyyy-MM-dd} til {fyEnd:yyyy-MM-dd})");
            }
        }

        aggregate.MarkPosted(
            command.TransactionId,
            command.PostedBy);
    }
}

public class DiscardJournalEntryDraftCommandHandler
    : CommandHandler<JournalEntryDraftAggregate, JournalEntryDraftId, DiscardJournalEntryDraftCommand>
{
    public override Task ExecuteAsync(
        JournalEntryDraftAggregate aggregate,
        DiscardJournalEntryDraftCommand command,
        CancellationToken cancellationToken)
    {
        aggregate.Discard(command.DiscardedBy);

        return Task.CompletedTask;
    }
}
