#nullable enable
using Baya.Domain.Common;

namespace Baya.Domain.Entities.Bnpl;

/// <summary>
/// One BNPL order, <b>1:1 with its <c>payment_transaction</c></b> (the <c>UNIQUE(payment_transaction_id)</c>
/// guard) — the single inbound settlement to reconcile plus the revert path. In Balinyaar's books a BNPL order
/// is a <b>card payment that lands net-of-fee</b>: there is nothing to amortize on our side (the provider owns
/// the customer's installments and 100% of default risk), so this is one row, not a plan+entries tree.
/// <para>
/// <see cref="Status"/> is a <b>forward-only</b> state machine (<see cref="BnplTransitions"/>) mutated only
/// through the cohesive mark-* methods; a replayed <c>settle</c>/<c>revert</c> that would re-drive a completed
/// transition throws (the handler treats an already-in-target state as an idempotent no-op), so the ledger is
/// never double-posted. Every amount is IRR <c>BIGINT</c> — currency is normalized to IRR at the provider
/// boundary, never here.
/// </para>
/// </summary>
public class BnplTransaction : BaseEntity<long>
{
    /// <summary>The 1:1 parent payment attempt — <c>UNIQUE</c> so exactly one BNPL row exists per order.</summary>
    public long PaymentTransactionId { get; set; }

    /// <summary>Selects the provider impl — <c>snapppay</c> / <c>digipay</c> / <c>tara</c> / <c>torobpay</c>.</summary>
    public string ProviderCode { get; set; } = null!;

    /// <summary>Balinyaar entity or partner center that is merchant-of-record for the order.</summary>
    public string MerchantOfRecord { get; set; } = null!;

    /// <summary>The provider payment token, issued at initiate and used for verify/settle/revert.</summary>
    public string? ExternalPaymentToken { get; private set; }

    /// <summary>The provider's own order/txn id, when returned.</summary>
    public string? ExternalTransactionId { get; private set; }

    /// <summary>The recorded eligibility outcome — a <see cref="BnplEligibilityStatus"/> code.</summary>
    public string? EligibilityStatus { get; set; }

    /// <summary>Gross order (IRR) = the booking's <c>gross_price_irr</c>. No floats, ever.</summary>
    public long OrderAmountIrr { get; set; }

    /// <summary>Net of provider commission actually received (IRR) — set at settle from the real settlement.</summary>
    public long? SettledAmountIrr { get; private set; }

    /// <summary>The provider's merchant discount (IRR) = a <b>platform expense</b>, never the nurse's — set at settle.</summary>
    public long? BnplCommissionIrr { get; private set; }

    /// <summary><c>IRR</c>/<c>TOMAN</c> at the boundary; normalized to IRR on the way in.</summary>
    public string Currency { get; set; } = "IRR";

    /// <summary>Informational only (default 4) — the installment schedule is owned by the provider.</summary>
    public byte InstallmentCount { get; set; } = 4;

    /// <summary>Guarded — a <see cref="BnplStatus"/> code, mutated only through the mark-* methods.</summary>
    public string Status { get; private set; } = BnplStatus.Eligible;

    /// <summary><b>Per-transaction</b>, contract-defined (daily/T+1–3/weekly) — nullable, never assumed instant.</summary>
    public DateTime? SettledAt { get; private set; }

    // ---- reversal path ----
    public string? RevertTransactionId { get; private set; }
    public long? RevertedAmountIrr { get; private set; }
    public DateTime? RevertedAt { get; private set; }

    /// <summary>The provider's own commission reversal — <b>nullable</b>, reconciled from the provider response,
    /// never hardcoded (some providers keep their fee on a refund).</summary>
    public long? ProviderCommissionReversedAmount { get; private set; }

    /// <summary><c>bnpl_revert</c> on a reversal.</summary>
    public string? RefundChannel { get; private set; }

    /// <summary>Raw verify/settle/revert payload for reconciliation and audit.</summary>
    public string? CallbackPayloadJson { get; set; }

    public DateTimeOffset? DeletedAt { get; set; }

    public bool CanTransitionTo(string target) => BnplTransitions.CanTransition(Status, target);

    private void Transition(string target)
    {
        if (!BnplTransitions.CanTransition(Status, target))
            throw new InvalidOperationException($"Illegal BNPL transition {Status} → {target}.");
        Status = target;
    }

    /// <summary>Records the provider token and transitions <c>eligible → token_issued</c>.</summary>
    public void MarkTokenIssued(string externalPaymentToken, string? externalTransactionId)
    {
        Transition(BnplStatus.TokenIssued);
        ExternalPaymentToken = externalPaymentToken;
        ExternalTransactionId ??= externalTransactionId;
    }

    /// <summary>Server-side amount + reference re-checked by the handler; transitions <c>token_issued → verified</c>.</summary>
    public void MarkVerified(string? externalTransactionId, string? callbackPayloadJson)
    {
        Transition(BnplStatus.Verified);
        ExternalTransactionId ??= externalTransactionId;
        CallbackPayloadJson = callbackPayloadJson ?? CallbackPayloadJson;
    }

    /// <summary>Records the actual settlement (net + commission + per-transaction <paramref name="settledAt"/>)
    /// and transitions <c>verified → settled</c>. Enforces <c>settled = order − commission</c> and non-negativity
    /// so a settled row can never carry an unbalanced split.</summary>
    public void MarkSettled(long settledAmountIrr, long commissionIrr, DateTime? settledAt, string? callbackPayloadJson)
    {
        if (commissionIrr < 0 || settledAmountIrr < 0)
            throw new InvalidOperationException("BNPL settlement amounts must be non-negative.");
        if (settledAmountIrr != OrderAmountIrr - commissionIrr)
            throw new InvalidOperationException(
                $"BNPL settlement would not reconcile: settled {settledAmountIrr} != order {OrderAmountIrr} − commission {commissionIrr}.");

        Transition(BnplStatus.Settled);
        SettledAmountIrr = settledAmountIrr;
        BnplCommissionIrr = commissionIrr;
        SettledAt = settledAt;
        CallbackPayloadJson = callbackPayloadJson ?? CallbackPayloadJson;
    }

    /// <summary>Records the provider-mediated reversal audit and transitions to <c>reverted</c>. The customer
    /// cash-back is async and owned by the provider — money flows customer ↔ provider ↔ Balinyaar only.</summary>
    public void MarkReverted(
        string? revertTransactionId, long revertedAmountIrr, long? providerCommissionReversedAmount,
        DateTime revertedAt, string? callbackPayloadJson)
    {
        Transition(BnplStatus.Reverted);
        RevertTransactionId = revertTransactionId;
        RevertedAmountIrr = revertedAmountIrr;
        ProviderCommissionReversedAmount = providerCommissionReversedAmount;
        RevertedAt = revertedAt;
        RefundChannel = Refunds.RefundChannel.BnplRevert;
        CallbackPayloadJson = callbackPayloadJson ?? CallbackPayloadJson;
    }

    public void MarkCancelled() => Transition(BnplStatus.Cancelled);

    public void MarkFailed() => Transition(BnplStatus.Failed);
}
