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

namespace CashLineERP.Domain.Finance;

public sealed class ForeignExchangeOverride : Entity
{
    public long Id { get; private set; }
    public string OverrideNumber { get; private set; } = string.Empty;
    public DateOnly EffectiveDate { get; private set; }
    public string BaseCurrencyCode { get; private set; } = "EGP";
    public string CurrencyCode { get; private set; } = "USD";
    public decimal OverrideRateToBase { get; private set; }
    public string ReasonCode { get; private set; } = string.Empty;
    public string? Notes { get; private set; }
    public long StatusId { get; private set; } = WorkflowStatusSeedIds.Draft;
    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 ForeignExchangeOverride(
        string overrideNumber,
        DateOnly effectiveDate,
        string baseCurrencyCode,
        string currencyCode,
        decimal overrideRateToBase,
        string reasonCode,
        string? notes)
    {
        if (string.IsNullOrWhiteSpace(overrideNumber))
        {
            throw new InvalidOperationException("FX override number is required.");
        }

        var normalizedBaseCurrencyCode = NormalizeRequiredCurrencyCode(baseCurrencyCode);
        var normalizedCurrencyCode = NormalizeRequiredCurrencyCode(currencyCode);

        if (string.Equals(normalizedBaseCurrencyCode, normalizedCurrencyCode, StringComparison.OrdinalIgnoreCase))
        {
            throw new InvalidOperationException("FX override currency cannot equal the base currency.");
        }

        if (overrideRateToBase <= 0m)
        {
            throw new InvalidOperationException("FX override rate to base must be greater than zero.");
        }

        var normalizedReasonCode = NormalizeRequiredReasonCode(reasonCode);

        OverrideNumber = overrideNumber.Trim().ToUpperInvariant();
        EffectiveDate = effectiveDate;
        BaseCurrencyCode = normalizedBaseCurrencyCode;
        CurrencyCode = normalizedCurrencyCode;
        OverrideRateToBase = overrideRateToBase;
        ReasonCode = normalizedReasonCode;
        Notes = NormalizeOptional(notes);
    }

    private ForeignExchangeOverride()
    {
    }

    public void Submit(long actorUserId)
    {
        if (StatusId != WorkflowStatusSeedIds.Draft && StatusId != WorkflowStatusSeedIds.Returned)
        {
            throw new InvalidOperationException("Only draft or returned FX overrides 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 FX overrides can be approved.");
        }

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

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

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

        return normalized;
    }

    private static string NormalizeRequiredReasonCode(string reasonCode)
    {
        var normalized = reasonCode?.Trim().ToUpperInvariant();
        if (string.IsNullOrWhiteSpace(normalized))
        {
            throw new InvalidOperationException("FX override reason code is required.");
        }

        return normalized;
    }

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