using CashLineERP.Domain.Common;
using CashLineERP.SharedKernel.Finance;
using CashLineERP.SharedKernel.Workflow;

namespace CashLineERP.Domain.Finance;

public sealed class FinanceAccountingRule : Entity
{
    public long Id { get; private set; }
    public string RuleCode { get; private set; } = string.Empty;
    public string RuleName { get; private set; } = string.Empty;
    public long GeneralLedgerAccountId { get; private set; }
    public string GeneralLedgerAccountCode { get; private set; } = string.Empty;
    public string GeneralLedgerAccountName { get; private set; } = string.Empty;
    public string AccountCategoryCode { get; private set; } = string.Empty;
    public string StatementSectionCode { get; private set; } = string.Empty;
    public string CashFlowCategoryCode { get; private set; } = string.Empty;
    public bool IsCashAccount { get; private set; }
    public int DisplayOrder { get; private set; }
    public DateOnly EffectiveFromDate { get; private set; }
    public DateOnly? EffectiveToDate { get; private set; }
    public long StatusId { get; private set; } = WorkflowStatusSeedIds.Draft;
    public bool IsActive { get; private set; }
    public string? Notes { get; private set; }
    public DateTime? SubmittedAtUtc { get; private set; }
    public long? SubmittedByUserId { get; private set; }
    public DateTime? ApprovedAtUtc { get; private set; }
    public long? ApprovedByUserId { get; private set; }
    public DateTime? ActivatedAtUtc { get; private set; }
    public long? ActivatedByUserId { get; private set; }

    public FinanceAccountingRule(
        string ruleCode,
        string ruleName,
        long generalLedgerAccountId,
        string generalLedgerAccountCode,
        string generalLedgerAccountName,
        string accountCategoryCode,
        string statementSectionCode,
        string? cashFlowCategoryCode,
        bool isCashAccount,
        int displayOrder,
        DateOnly effectiveFromDate,
        DateOnly? effectiveToDate,
        string? notes)
    {
        if (string.IsNullOrWhiteSpace(ruleCode))
        {
            throw new InvalidOperationException("Finance accounting rule code is required.");
        }

        if (string.IsNullOrWhiteSpace(ruleName))
        {
            throw new InvalidOperationException("Finance accounting rule name is required.");
        }

        if (generalLedgerAccountId <= 0)
        {
            throw new InvalidOperationException("Finance accounting rule requires a valid GL account identifier.");
        }

        if (string.IsNullOrWhiteSpace(generalLedgerAccountCode))
        {
            throw new InvalidOperationException("Finance accounting rule GL account code is required.");
        }

        if (string.IsNullOrWhiteSpace(generalLedgerAccountName))
        {
            throw new InvalidOperationException("Finance accounting rule GL account name is required.");
        }

        if (displayOrder < 0)
        {
            throw new InvalidOperationException("Finance accounting rule display order cannot be negative.");
        }

        if (effectiveToDate.HasValue && effectiveToDate.Value < effectiveFromDate)
        {
            throw new InvalidOperationException("Finance accounting rule effective-to date cannot be earlier than effective-from date.");
        }

        RuleCode = ruleCode.Trim().ToUpperInvariant();
        RuleName = ruleName.Trim();
        GeneralLedgerAccountId = generalLedgerAccountId;
        GeneralLedgerAccountCode = generalLedgerAccountCode.Trim().ToUpperInvariant();
        GeneralLedgerAccountName = generalLedgerAccountName.Trim();
        AccountCategoryCode = FinanceAccountingRuleCatalog.NormalizeAccountCategoryCode(accountCategoryCode);
        StatementSectionCode = FinanceAccountingRuleCatalog.NormalizeStatementSectionCode(statementSectionCode);
        CashFlowCategoryCode = FinanceAccountingRuleCatalog.NormalizeCashFlowCategoryCode(cashFlowCategoryCode);
        IsCashAccount = isCashAccount;
        DisplayOrder = displayOrder;
        EffectiveFromDate = effectiveFromDate;
        EffectiveToDate = effectiveToDate;
        Notes = string.IsNullOrWhiteSpace(notes) ? null : notes.Trim();
    }

    private FinanceAccountingRule()
    {
    }

    public void Submit(long actorUserId)
    {
        if (StatusId != WorkflowStatusSeedIds.Draft && StatusId != WorkflowStatusSeedIds.Returned)
        {
            throw new InvalidOperationException("Only draft or returned accounting rules can be submitted.");
        }

        StatusId = WorkflowStatusSeedIds.Submitted;
        SubmittedAtUtc = DateTime.UtcNow;
        SubmittedByUserId = actorUserId;
        ApprovedAtUtc = null;
        ApprovedByUserId = null;
        Touch(actorUserId);
    }

    public void Approve(long actorUserId)
    {
        if (StatusId == WorkflowStatusSeedIds.Approved)
        {
            return;
        }

        if (StatusId != WorkflowStatusSeedIds.Submitted)
        {
            throw new InvalidOperationException("Only submitted accounting rules can be approved.");
        }

        if (SubmittedByUserId == actorUserId)
        {
            throw new InvalidOperationException("Accounting rule maker-checker control prohibits self-approval.");
        }

        StatusId = WorkflowStatusSeedIds.Approved;
        ApprovedAtUtc = DateTime.UtcNow;
        ApprovedByUserId = actorUserId;
        Touch(actorUserId);
    }

    public void Activate(long actorUserId)
    {
        if (StatusId != WorkflowStatusSeedIds.Approved)
        {
            throw new InvalidOperationException("Only approved accounting rules can be activated.");
        }

        IsActive = true;
        ActivatedAtUtc = DateTime.UtcNow;
        ActivatedByUserId = actorUserId;
        Touch(actorUserId);
    }

    public bool AppliesOn(DateOnly businessDate) =>
        IsActive
        && StatusId == WorkflowStatusSeedIds.Approved
        && businessDate >= EffectiveFromDate
        && (!EffectiveToDate.HasValue || businessDate <= EffectiveToDate.Value);
}
