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

namespace CashLineERP.Domain.Finance;

public sealed class WalletRule : 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? BranchId { get; private set; }
    public string PartyTypeCode { get; private set; } = string.Empty;
    public string TransactionTypeCode { get; private set; } = string.Empty;
    public string WalletSchemeCode { get; private set; } = string.Empty;
    public string IntegrationProviderCode { get; private set; } = string.Empty;
    public string CurrencyCode { get; private set; } = string.Empty;
    public decimal? MinimumAmount { get; private set; }
    public decimal? MaximumAmount { get; private set; }
    public int SettlementLeadDays { get; private set; }
    public decimal FeePercent { get; private set; }
    public decimal FlatFeeAmount { get; private set; }
    public int Priority { 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 WalletRule(
        string ruleCode,
        string ruleName,
        long? branchId,
        string partyTypeCode,
        string transactionTypeCode,
        string walletSchemeCode,
        string integrationProviderCode,
        string currencyCode,
        decimal? minimumAmount,
        decimal? maximumAmount,
        int settlementLeadDays,
        decimal feePercent,
        decimal flatFeeAmount,
        int priority,
        DateOnly effectiveFromDate,
        DateOnly? effectiveToDate,
        string? notes)
    {
        if (string.IsNullOrWhiteSpace(ruleCode))
        {
            throw new InvalidOperationException("Wallet rule code is required.");
        }

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

        if (branchId.HasValue && branchId.Value <= 0)
        {
            throw new InvalidOperationException("Wallet rule branch must be a valid positive identifier when provided.");
        }

        if (minimumAmount.HasValue && minimumAmount.Value < 0m)
        {
            throw new InvalidOperationException("Wallet rule minimum amount cannot be negative.");
        }

        if (maximumAmount.HasValue && maximumAmount.Value <= 0m)
        {
            throw new InvalidOperationException("Wallet rule maximum amount must be greater than zero.");
        }

        if (minimumAmount.HasValue && maximumAmount.HasValue && minimumAmount.Value > maximumAmount.Value)
        {
            throw new InvalidOperationException("Wallet rule minimum amount cannot exceed maximum amount.");
        }

        if (settlementLeadDays is < 0 or > 30)
        {
            throw new InvalidOperationException("Wallet rule settlement lead days must be between 0 and 30.");
        }

        if (feePercent is < 0m or > 100m)
        {
            throw new InvalidOperationException("Wallet rule fee percent must be between 0 and 100.");
        }

        if (flatFeeAmount < 0m)
        {
            throw new InvalidOperationException("Wallet rule flat fee amount cannot be negative.");
        }

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

        RuleCode = ruleCode.Trim().ToUpperInvariant();
        RuleName = ruleName.Trim();
        BranchId = branchId;
        PartyTypeCode = WalletRuleCatalog.NormalizePartyTypeCode(partyTypeCode);
        TransactionTypeCode = WalletRuleCatalog.NormalizeTransactionTypeCode(transactionTypeCode);
        WalletSchemeCode = WalletRuleCatalog.NormalizeWalletSchemeCode(walletSchemeCode);
        IntegrationProviderCode = WalletRuleCatalog.NormalizeIntegrationProviderCode(integrationProviderCode);
        CurrencyCode = WalletRuleCatalog.NormalizeCurrencyCode(currencyCode);
        MinimumAmount = minimumAmount;
        MaximumAmount = maximumAmount;
        SettlementLeadDays = settlementLeadDays;
        FeePercent = feePercent;
        FlatFeeAmount = flatFeeAmount;
        Priority = priority;
        EffectiveFromDate = effectiveFromDate;
        EffectiveToDate = effectiveToDate;
        Notes = string.IsNullOrWhiteSpace(notes) ? null : notes.Trim();
    }

    private WalletRule()
    {
    }

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

        if (SubmittedByUserId == actorUserId)
        {
            throw new InvalidOperationException("Wallet 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 wallet rules can be activated.");
        }

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

    public bool AppliesTo(
        long? branchId,
        string partyTypeCode,
        string transactionTypeCode,
        string walletSchemeCode,
        string currencyCode,
        decimal requestedAmount,
        DateOnly evaluationDate) =>
        IsActive
        && StatusId == WorkflowStatusSeedIds.Approved
        && (!BranchId.HasValue || (branchId.HasValue && BranchId.Value == branchId.Value))
        && (PartyTypeCode == "ANY" || string.Equals(PartyTypeCode, WalletRuleCatalog.NormalizePartyTypeCode(partyTypeCode), StringComparison.OrdinalIgnoreCase))
        && string.Equals(TransactionTypeCode, WalletRuleCatalog.NormalizeTransactionTypeCode(transactionTypeCode), StringComparison.OrdinalIgnoreCase)
        && string.Equals(WalletSchemeCode, WalletRuleCatalog.NormalizeWalletSchemeCode(walletSchemeCode), StringComparison.OrdinalIgnoreCase)
        && string.Equals(CurrencyCode, WalletRuleCatalog.NormalizeCurrencyCode(currencyCode), StringComparison.OrdinalIgnoreCase)
        && (!MinimumAmount.HasValue || requestedAmount >= MinimumAmount.Value)
        && (!MaximumAmount.HasValue || requestedAmount <= MaximumAmount.Value)
        && evaluationDate >= EffectiveFromDate
        && (!EffectiveToDate.HasValue || evaluationDate <= EffectiveToDate.Value);

    public int GetSpecificityScore() =>
        (BranchId.HasValue ? 2 : 0)
        + (!string.Equals(PartyTypeCode, "ANY", StringComparison.OrdinalIgnoreCase) ? 1 : 0);
}
