using Books.Api.Domain.FiscalYears.Events;
using EventFlow.Aggregates;

namespace Books.Api.Domain.FiscalYears;

public class FiscalYearAggregate(FiscalYearId id) : AggregateRoot<FiscalYearAggregate, FiscalYearId>(id),
    IEmit<FiscalYearCreatedEvent>,
    IEmit<FiscalYearClosedEvent>,
    IEmit<FiscalYearReopenedEvent>,
    IEmit<FiscalYearLockedEvent>,
    IEmit<OpeningBalancePostedEvent>
{
    private bool _isCreated;
    private FiscalYearStatus _status = FiscalYearStatus.Open;
    private bool _openingBalancePosted;

    public void Apply(FiscalYearCreatedEvent e)
    {
        _isCreated = true;
        _status = FiscalYearStatus.Open;
    }

    public void Apply(FiscalYearClosedEvent e) => _status = FiscalYearStatus.Closed;

    public void Apply(FiscalYearReopenedEvent e) => _status = FiscalYearStatus.Open;

    public void Apply(FiscalYearLockedEvent e) => _status = FiscalYearStatus.Locked;

    public void Apply(OpeningBalancePostedEvent e) => _openingBalancePosted = true;

    public void Create(
        string companyId,
        string name,
        DateOnly startDate,
        DateOnly endDate,
        bool isFirstFiscalYear = false,
        bool isReorganization = false)
    {
        if (_isCreated)
            throw new DomainException("FISCAL_YEAR_EXISTS", "Fiscal year already exists", "Regnskabsaar eksisterer allerede");

        if (string.IsNullOrWhiteSpace(companyId))
            throw new DomainException("COMPANY_REQUIRED", "Company ID is required", "Virksomheds-ID er paakraevet");

        if (string.IsNullOrWhiteSpace(name))
            throw new DomainException("NAME_REQUIRED", "Fiscal year name is required", "Regnskabsaarsnavn er paakraevet");

        if (endDate <= startDate)
            throw new DomainException("INVALID_DATE_RANGE", "End date must be after start date", "Slutdato skal vaere efter startdato");

        // Calculate duration in months
        var months = ((endDate.Year - startDate.Year) * 12) + endDate.Month - startDate.Month;

        // If endDate is NOT the 1st of month, we're including that month
        // (i.e., Dec 31 means include December, but Jan 1 means exclude January)
        if (endDate.Day > 1)
            months++;

        // Per Årsregnskabsloven §15 (Danish Accounting Act):
        // - Standard fiscal year: Exactly 12 months
        // - First fiscal year: Can be 6-18 months
        // - Reorganization: Can be up to 18 months
        if (isFirstFiscalYear || isReorganization)
        {
            // Allow flexible duration for first year or reorganization
            if (months < 6 || months > 18)
                throw new DomainException("INVALID_DURATION",
                    "Fiscal year must be 6-18 months for first year or reorganization (Årsregnskabsloven §15)",
                    "Regnskabsår skal være 6-18 måneder for første år eller omlægning (Årsregnskabsloven §15)");
        }
        else
        {
            // Standard fiscal year must be exactly 12 months
            if (months != 12)
                throw new DomainException("INVALID_STANDARD_DURATION",
                    "Standard fiscal year must be exactly 12 months. Use isFirstFiscalYear or isReorganization for non-standard duration (Årsregnskabsloven §15)",
                    "Standard regnskabsår skal være præcis 12 måneder. Brug isFirstFiscalYear eller isReorganization for afvigende varighed (Årsregnskabsloven §15)");
        }

        Emit(new FiscalYearCreatedEvent(companyId, name.Trim(), startDate, endDate));
    }

    public void Close(string closedBy)
    {
        if (!_isCreated)
            throw new DomainException("FISCAL_YEAR_NOT_FOUND", "Fiscal year does not exist", "Regnskabsaar findes ikke");

        if (_status == FiscalYearStatus.Closed)
            throw new DomainException("ALREADY_CLOSED", "Fiscal year is already closed", "Regnskabsaar er allerede lukket");

        if (_status == FiscalYearStatus.Locked)
            throw new DomainException("FISCAL_YEAR_LOCKED", "Fiscal year is locked and cannot be modified", "Regnskabsaar er laast og kan ikke aendres");

        if (string.IsNullOrWhiteSpace(closedBy))
            throw new DomainException("CLOSED_BY_REQUIRED", "User who closed the year is required", "Bruger der lukker aaret er paakraevet");

        Emit(new FiscalYearClosedEvent(closedBy));
    }

    public void Reopen(string reopenedBy)
    {
        if (!_isCreated)
            throw new DomainException("FISCAL_YEAR_NOT_FOUND", "Fiscal year does not exist", "Regnskabsaar findes ikke");

        if (_status == FiscalYearStatus.Open)
            throw new DomainException("ALREADY_OPEN", "Fiscal year is already open", "Regnskabsaar er allerede aabent");

        if (_status == FiscalYearStatus.Locked)
            throw new DomainException("FISCAL_YEAR_LOCKED", "Locked fiscal years cannot be reopened", "Laaste regnskabsaar kan ikke genåbnes");

        if (string.IsNullOrWhiteSpace(reopenedBy))
            throw new DomainException("REOPENED_BY_REQUIRED", "User who reopened the year is required", "Bruger der genåbner aaret er paakraevet");

        Emit(new FiscalYearReopenedEvent(reopenedBy));
    }

    public void Lock(string lockedBy)
    {
        if (!_isCreated)
            throw new DomainException("FISCAL_YEAR_NOT_FOUND", "Fiscal year does not exist", "Regnskabsaar findes ikke");

        if (_status == FiscalYearStatus.Locked)
            throw new DomainException("ALREADY_LOCKED", "Fiscal year is already locked", "Regnskabsaar er allerede laast");

        if (_status != FiscalYearStatus.Closed)
            throw new DomainException("MUST_BE_CLOSED", "Fiscal year must be closed before it can be locked", "Regnskabsaar skal vaere lukket foer det kan laases");

        if (string.IsNullOrWhiteSpace(lockedBy))
            throw new DomainException("LOCKED_BY_REQUIRED", "User who locked the year is required", "Bruger der laaser aaret er paakraevet");

        Emit(new FiscalYearLockedEvent(lockedBy));
    }

    public void MarkOpeningBalancePosted()
    {
        if (!_isCreated)
            throw new DomainException("FISCAL_YEAR_NOT_FOUND", "Fiscal year does not exist", "Regnskabsaar findes ikke");

        if (_openingBalancePosted)
            throw new DomainException("OPENING_BALANCE_ALREADY_POSTED", "Opening balance has already been posted", "Primosaldo er allerede bogfoert");

        Emit(new OpeningBalancePostedEvent());
    }
}
