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

namespace CashLineERP.Domain.Finance;

public sealed class FinanceCloseJournal : Entity
{
    public long Id { get; private set; }

    public string CloseJournalNumber { get; private set; } = string.Empty;

    public long AccountingPeriodId { get; private set; }

    public long? BranchId { get; private set; }

    public string JournalClassCode { get; private set; } = string.Empty;

    public DateOnly PostingDate { get; private set; }

    public DateOnly DocumentDate { get; private set; }

    public string? Notes { get; private set; }

    public long StatusId { get; private set; } = WorkflowStatusSeedIds.Draft;

    public decimal TotalDebitAmount { get; private set; }

    public decimal TotalCreditAmount { 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? PostedAtUtc { get; private set; }

    public long? PostedByUserId { get; private set; }

    public long? GeneralLedgerBatchId { get; private set; }

    public List<FinanceCloseJournalLine> Lines { get; private set; } = [];

    public FinanceCloseJournal(
        string closeJournalNumber,
        long accountingPeriodId,
        long? branchId,
        string journalClassCode,
        DateOnly postingDate,
        DateOnly documentDate,
        string? notes)
    {
        CloseJournalNumber = closeJournalNumber;
        AccountingPeriodId = accountingPeriodId;
        BranchId = branchId;
        JournalClassCode = journalClassCode;
        PostingDate = postingDate;
        DocumentDate = documentDate;
        Notes = notes;
    }

    private FinanceCloseJournal()
    {
    }

    public void AddLine(
        int lineNumber,
        long generalLedgerAccountId,
        long? branchId,
        long? departmentId,
        long? partyId,
        long? itemId,
        decimal debitAmount,
        decimal creditAmount,
        string? currencyCode,
        decimal fxRate,
        string? lineDescription)
    {
        if (debitAmount < 0m || creditAmount < 0m)
        {
            throw new InvalidOperationException("Close journal line amounts cannot be negative.");
        }

        var hasDebit = debitAmount > 0m;
        var hasCredit = creditAmount > 0m;
        if (hasDebit == hasCredit)
        {
            throw new InvalidOperationException("Close journal line must contain either a debit amount or a credit amount.");
        }

        Lines.Add(new FinanceCloseJournalLine(
            financeCloseJournalId: Id,
            lineNumber: lineNumber,
            generalLedgerAccountId: generalLedgerAccountId,
            branchId: branchId,
            departmentId: departmentId,
            partyId: partyId,
            itemId: itemId,
            debitAmount: debitAmount,
            creditAmount: creditAmount,
            currencyCode: currencyCode,
            fxRate: fxRate,
            lineDescription: lineDescription));

        RecalculateTotals();
    }

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

        if (Lines.Count < 2)
        {
            throw new InvalidOperationException("Close journal must contain at least two lines before submission.");
        }

        if (TotalDebitAmount != TotalCreditAmount)
        {
            throw new InvalidOperationException("Close journal must be balanced before submission.");
        }

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

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

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

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

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

    public void Post(long actorUserId, long generalLedgerBatchId)
    {
        if (StatusId == WorkflowStatusSeedIds.Posted)
        {
            GeneralLedgerBatchId ??= generalLedgerBatchId;
            return;
        }

        if (StatusId != WorkflowStatusSeedIds.Approved)
        {
            throw new InvalidOperationException("Close journal must be approved before posting.");
        }

        if (TotalDebitAmount != TotalCreditAmount)
        {
            throw new InvalidOperationException("Close journal must remain balanced before posting.");
        }

        StatusId = WorkflowStatusSeedIds.Posted;
        GeneralLedgerBatchId = generalLedgerBatchId;
        PostedAtUtc = DateTime.UtcNow;
        PostedByUserId = actorUserId;
        Touch(actorUserId);
    }

    private void RecalculateTotals()
    {
        TotalDebitAmount = Lines.Sum(line => line.DebitAmount);
        TotalCreditAmount = Lines.Sum(line => line.CreditAmount);
    }
}
