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

namespace CashLineERP.Domain.Finance;

public sealed class CardTokenReference : Entity
{
    public long Id { get; private set; }
    public string CardTokenReferenceNumber { get; private set; } = string.Empty;
    public long PartyId { get; private set; }
    public string PartyTypeCode { get; private set; } = string.Empty;
    public string IntegrationProviderCode { get; private set; } = string.Empty;
    public string UsageCode { get; private set; } = string.Empty;
    public string CardBrandCode { get; private set; } = string.Empty;
    public string MaskedPan { get; private set; } = string.Empty;
    public int ExpiryMonth { get; private set; }
    public int ExpiryYear { get; private set; }
    public string TokenReference { get; private set; } = string.Empty;
    public string? ExternalCustomerReference { get; private set; }
    public bool IsDefault { 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 DateTime? SuspendedAtUtc { get; private set; }
    public long? SuspendedByUserId { get; private set; }
    public string? SuspensionReasonCode { get; private set; }
    public string? SuspensionNotes { get; private set; }

    public CardTokenReference(
        string cardTokenReferenceNumber,
        long partyId,
        string partyTypeCode,
        string integrationProviderCode,
        string usageCode,
        string cardBrandCode,
        string maskedPan,
        int expiryMonth,
        int expiryYear,
        string tokenReference,
        string? externalCustomerReference,
        bool isDefault,
        string? notes)
    {
        if (string.IsNullOrWhiteSpace(cardTokenReferenceNumber))
        {
            throw new InvalidOperationException("Card token reference number is required.");
        }

        if (partyId <= 0)
        {
            throw new InvalidOperationException("Card token reference requires a valid party.");
        }

        if (expiryMonth is < 1 or > 12)
        {
            throw new InvalidOperationException("Card expiry month must be between 1 and 12.");
        }

        if (expiryYear < 2020)
        {
            throw new InvalidOperationException("Card expiry year must be a valid calendar year.");
        }

        CardTokenReferenceNumber = cardTokenReferenceNumber.Trim().ToUpperInvariant();
        PartyId = partyId;
        PartyTypeCode = CardTokenReferenceCatalog.NormalizePartyTypeCode(partyTypeCode);
        IntegrationProviderCode = CardTokenReferenceCatalog.NormalizeIntegrationProviderCode(integrationProviderCode);
        UsageCode = CardTokenReferenceCatalog.NormalizeUsageCode(usageCode);
        CardBrandCode = CardTokenReferenceCatalog.NormalizeCardBrandCode(cardBrandCode);
        MaskedPan = CardTokenReferenceCatalog.NormalizeMaskedPan(maskedPan);
        ExpiryMonth = expiryMonth;
        ExpiryYear = expiryYear;
        TokenReference = CardTokenReferenceCatalog.NormalizeTokenReference(tokenReference);
        ExternalCustomerReference = string.IsNullOrWhiteSpace(externalCustomerReference) ? null : externalCustomerReference.Trim();
        IsDefault = isDefault;
        Notes = string.IsNullOrWhiteSpace(notes) ? null : notes.Trim();
    }

    private CardTokenReference()
    {
    }

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

        if (SubmittedByUserId == actorUserId)
        {
            throw new InvalidOperationException("Card token reference 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 card token references can be activated.");
        }

        IsActive = true;
        ActivatedAtUtc = DateTime.UtcNow;
        ActivatedByUserId = actorUserId;
        SuspendedAtUtc = null;
        SuspendedByUserId = null;
        SuspensionReasonCode = null;
        SuspensionNotes = null;
        Touch(actorUserId);
    }

    public void Suspend(long actorUserId, string reasonCode, string? notes)
    {
        if (!IsActive)
        {
            throw new InvalidOperationException("Only active card token references can be suspended.");
        }

        var normalizedReasonCode = reasonCode?.Trim().ToUpperInvariant();
        if (string.IsNullOrWhiteSpace(normalizedReasonCode))
        {
            throw new InvalidOperationException("Card token suspension reason code is required.");
        }

        IsActive = false;
        IsDefault = false;
        SuspendedAtUtc = DateTime.UtcNow;
        SuspendedByUserId = actorUserId;
        SuspensionReasonCode = normalizedReasonCode;
        SuspensionNotes = string.IsNullOrWhiteSpace(notes) ? null : notes.Trim();
        Touch(actorUserId);
    }

    public void UnsetDefault(long actorUserId)
    {
        if (!IsDefault)
        {
            return;
        }

        IsDefault = false;
        Touch(actorUserId);
    }
}
