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

namespace CashLineERP.Domain.Finance;

public sealed class ForeignExchangeRevaluation : Entity
{
    public long Id { get; private set; }
    public string RevaluationNumber { get; private set; } = string.Empty;
    public long AccountingPeriodId { get; private set; }
    public DateOnly RevaluationDate { get; private set; }
    public string BaseCurrencyCode { get; private set; } = "EGP";
    public string? Notes { get; private set; }
    public long StatusId { get; private set; } = WorkflowStatusSeedIds.Draft;
    public decimal TotalGainBaseAmount { get; private set; }
    public decimal TotalLossBaseAmount { get; private set; }
    public decimal NetVarianceBaseAmount { 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<ForeignExchangeRevaluationLine> Lines { get; private set; } = [];

    public ForeignExchangeRevaluation(
        string revaluationNumber,
        long accountingPeriodId,
        DateOnly revaluationDate,
        string baseCurrencyCode,
        string? notes)
    {
        if (string.IsNullOrWhiteSpace(revaluationNumber))
        {
            throw new InvalidOperationException("FX revaluation number is required.");
        }

        if (accountingPeriodId <= 0)
        {
            throw new InvalidOperationException("FX revaluation requires a valid accounting period.");
        }

        RevaluationNumber = revaluationNumber.Trim().ToUpperInvariant();
        AccountingPeriodId = accountingPeriodId;
        RevaluationDate = revaluationDate;
        BaseCurrencyCode = NormalizeRequiredCurrencyCode(baseCurrencyCode);
        Notes = NormalizeOptional(notes);
    }

    private ForeignExchangeRevaluation()
    {
    }

    public void AddLine(
        int lineNumber,
        string sourceTypeCode,
        long sourceDocumentId,
        string sourceDocumentNumber,
        long sourceGeneralLedgerAccountId,
        long? branchId,
        long? partyId,
        DateOnly sourceDocumentDate,
        string currencyCode,
        decimal foreignAmount,
        decimal carryingRateToBase,
        decimal revaluationRateToBase,
        decimal carryingBaseAmount,
        decimal revaluedBaseAmount,
        decimal varianceBaseAmount)
    {
        if (varianceBaseAmount == 0m)
        {
            throw new InvalidOperationException("FX revaluation line must carry a non-zero base variance.");
        }

        Lines.Add(new ForeignExchangeRevaluationLine(
            foreignExchangeRevaluationId: Id,
            lineNumber: lineNumber,
            sourceTypeCode: sourceTypeCode,
            sourceDocumentId: sourceDocumentId,
            sourceDocumentNumber: sourceDocumentNumber,
            sourceGeneralLedgerAccountId: sourceGeneralLedgerAccountId,
            branchId: branchId,
            partyId: partyId,
            sourceDocumentDate: sourceDocumentDate,
            currencyCode: currencyCode,
            foreignAmount: foreignAmount,
            carryingRateToBase: carryingRateToBase,
            revaluationRateToBase: revaluationRateToBase,
            carryingBaseAmount: carryingBaseAmount,
            revaluedBaseAmount: revaluedBaseAmount,
            varianceBaseAmount: varianceBaseAmount));

        RecalculateTotals();
    }

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

        if (Lines.Count == 0)
        {
            throw new InvalidOperationException("FX revaluation requires at least one exposure line before submission.");
        }

        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 FX revaluations can be approved.");
        }

        if (SubmittedByUserId == actorUserId)
        {
            throw new InvalidOperationException("FX revaluation 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("FX revaluation must be approved before posting.");
        }

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

    private void RecalculateTotals()
    {
        TotalGainBaseAmount = Lines.Where(line => line.VarianceBaseAmount > 0m).Sum(line => line.VarianceBaseAmount);
        TotalLossBaseAmount = Lines.Where(line => line.VarianceBaseAmount < 0m).Sum(line => Math.Abs(line.VarianceBaseAmount));
        NetVarianceBaseAmount = Lines.Sum(line => line.VarianceBaseAmount);
    }

    private static string NormalizeRequiredCurrencyCode(string currencyCode)
    {
        var normalized = currencyCode?.Trim().ToUpperInvariant();
        if (string.IsNullOrWhiteSpace(normalized))
        {
            throw new InvalidOperationException("FX revaluation base currency is required.");
        }

        return normalized;
    }

    private static string? NormalizeOptional(string? value) =>
        string.IsNullOrWhiteSpace(value) ? null : value.Trim();
}
