package model

import (
	"strings"
	"time"

	"github.com/tech/sendico/pkg/db/storable"
	"github.com/tech/sendico/pkg/discovery"
	"github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/model/account_role"
	"github.com/tech/sendico/pkg/mservice"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
)

// PaymentKind captures the orchestrator intent type.
type PaymentKind string

const (
	PaymentKindUnspecified      PaymentKind = "unspecified"
	PaymentKindPayout           PaymentKind = "payout"
	PaymentKindInternalTransfer PaymentKind = "internal_transfer"
	PaymentKindFXConversion     PaymentKind = "fx_conversion"
)

// SettlementMode defines how fees/FX variance is handled.
type SettlementMode string

const (
	SettlementModeUnspecified SettlementMode = "unspecified"
	SettlementModeFixSource   SettlementMode = "fix_source"
	SettlementModeFixReceived SettlementMode = "fix_received"
)

// FeeTreatment defines who covers fees in settlement math.
type FeeTreatment string

const (
	FeeTreatmentUnspecified           FeeTreatment = "unspecified"
	FeeTreatmentAddToSource           FeeTreatment = "add_to_source"
	FeeTreatmentDeductFromDestination FeeTreatment = "deduct_from_destination"
)

// CommitPolicy controls when a step is committed during orchestration.
type CommitPolicy string

const (
	CommitPolicyUnspecified   CommitPolicy = "UNSPECIFIED"
	CommitPolicyImmediate     CommitPolicy = "IMMEDIATE"
	CommitPolicyAfterSuccess  CommitPolicy = "AFTER_SUCCESS"
	CommitPolicyAfterFailure  CommitPolicy = "AFTER_FAILURE"
	CommitPolicyAfterCanceled CommitPolicy = "AFTER_TERMINAL"
)

// PaymentState enumerates lifecycle phases.
type PaymentState string

const (
	PaymentStateUnspecified   PaymentState = "unspecified"
	PaymentStateAccepted      PaymentState = "accepted"
	PaymentStateFundsReserved PaymentState = "funds_reserved"
	PaymentStateSubmitted     PaymentState = "submitted"
	PaymentStateSuccess       PaymentState = "success"
	PaymentStateSettled       PaymentState = "settled"
	PaymentStateFailed        PaymentState = "failed"
	PaymentStateCancelled     PaymentState = "cancelled"
)

// PaymentFailureCode captures terminal reasons.
type PaymentFailureCode string

const (
	PaymentFailureCodeUnspecified PaymentFailureCode = "unspecified"
	PaymentFailureCodeBalance     PaymentFailureCode = "balance"
	PaymentFailureCodeLedger      PaymentFailureCode = "ledger"
	PaymentFailureCodeFX          PaymentFailureCode = "fx"
	PaymentFailureCodeChain       PaymentFailureCode = "chain"
	PaymentFailureCodeFees        PaymentFailureCode = "fees"
	PaymentFailureCodePolicy      PaymentFailureCode = "policy"
	PaymentFailureCodeSettlement  PaymentFailureCode = "settlement"
)

// Rail identifies a payment rail for orchestration.
type Rail = discovery.Rail

// RailOperation identifies an explicit action within a payment plan.
type RailOperation = discovery.RailOperation

// RailCapabilities are declared per gateway instance.
type RailCapabilities struct {
	CanPayIn               bool `bson:"canPayIn,omitempty" json:"canPayIn,omitempty"`
	CanPayOut              bool `bson:"canPayOut,omitempty" json:"canPayOut,omitempty"`
	CanReadBalance         bool `bson:"canReadBalance,omitempty" json:"canReadBalance,omitempty"`
	CanSendFee             bool `bson:"canSendFee,omitempty" json:"canSendFee,omitempty"`
	RequiresObserveConfirm bool `bson:"requiresObserveConfirm,omitempty" json:"requiresObserveConfirm,omitempty"`
	CanBlock               bool `bson:"canBlock,omitempty" json:"canBlock,omitempty"`
	CanRelease             bool `bson:"canRelease,omitempty" json:"canRelease,omitempty"`
}

// LimitsOverride applies per-currency overrides for limits.
type LimitsOverride struct {
	MaxVolume string `bson:"maxVolume,omitempty" json:"maxVolume,omitempty"`
	MinAmount string `bson:"minAmount,omitempty" json:"minAmount,omitempty"`
	MaxAmount string `bson:"maxAmount,omitempty" json:"maxAmount,omitempty"`
	MaxFee    string `bson:"maxFee,omitempty" json:"maxFee,omitempty"`
	MaxOps    int    `bson:"maxOps,omitempty" json:"maxOps,omitempty"`
}

// Limits define time-bucketed and per-tx constraints.
type Limits struct {
	MinAmount      string                    `bson:"minAmount,omitempty" json:"minAmount,omitempty"`
	MaxAmount      string                    `bson:"maxAmount,omitempty" json:"maxAmount,omitempty"`
	PerTxMaxFee    string                    `bson:"perTxMaxFee,omitempty" json:"perTxMaxFee,omitempty"`
	PerTxMinAmount string                    `bson:"perTxMinAmount,omitempty" json:"perTxMinAmount,omitempty"`
	PerTxMaxAmount string                    `bson:"perTxMaxAmount,omitempty" json:"perTxMaxAmount,omitempty"`
	VolumeLimit    map[string]string         `bson:"volumeLimit,omitempty" json:"volumeLimit,omitempty"`
	VelocityLimit  map[string]int            `bson:"velocityLimit,omitempty" json:"velocityLimit,omitempty"`
	CurrencyLimits map[string]LimitsOverride `bson:"currencyLimits,omitempty" json:"currencyLimits,omitempty"`
}

// GatewayInstanceDescriptor standardizes gateway instance self-declaration.
type GatewayInstanceDescriptor struct {
	ID           string           `bson:"id" json:"id"`
	InstanceID   string           `bson:"instanceId,omitempty" json:"instanceId,omitempty"`
	Rail         Rail             `bson:"rail" json:"rail"`
	Network      string           `bson:"network,omitempty" json:"network,omitempty"`
	InvokeURI    string           `bson:"invokeUri,omitempty" json:"invokeUri,omitempty"`
	Currencies   []string         `bson:"currencies,omitempty" json:"currencies,omitempty"`
	Operations   []RailOperation  `bson:"operations,omitempty" json:"operations,omitempty"`
	Capabilities RailCapabilities `bson:"capabilities,omitempty" json:"capabilities,omitempty"`
	Limits       Limits           `bson:"limits,omitempty" json:"limits,omitempty"`
	Version      string           `bson:"version,omitempty" json:"version,omitempty"`
	IsEnabled    bool             `bson:"isEnabled" json:"isEnabled"`
}

// PaymentEndpointType indicates how value should be routed.
type PaymentEndpointType string

const (
	EndpointTypeUnspecified   PaymentEndpointType = "unspecified"
	EndpointTypeLedger        PaymentEndpointType = "ledger"
	EndpointTypeManagedWallet PaymentEndpointType = "managed_wallet"
	EndpointTypeExternalChain PaymentEndpointType = "external_chain"
	EndpointTypeCard          PaymentEndpointType = "card"
)

// LedgerEndpoint describes ledger routing.
type LedgerEndpoint struct {
	LedgerAccountRef       string `bson:"ledgerAccountRef" json:"ledgerAccountRef"`
	ContraLedgerAccountRef string `bson:"contraLedgerAccountRef,omitempty" json:"contraLedgerAccountRef,omitempty"`
}

// ManagedWalletEndpoint describes managed wallet routing.
type ManagedWalletEndpoint struct {
	ManagedWalletRef string              `bson:"managedWalletRef" json:"managedWalletRef"`
	Asset            *paymenttypes.Asset `bson:"asset,omitempty" json:"asset,omitempty"`
}

// ExternalChainEndpoint describes an external address.
type ExternalChainEndpoint struct {
	Asset   *paymenttypes.Asset `bson:"asset,omitempty" json:"asset,omitempty"`
	Address string              `bson:"address" json:"address"`
	Memo    string              `bson:"memo,omitempty" json:"memo,omitempty"`
}

// CardEndpoint describes a card payout destination.
type CardEndpoint struct {
	Pan               string `bson:"pan,omitempty" json:"pan,omitempty"`
	Token             string `bson:"token,omitempty" json:"token,omitempty"`
	Cardholder        string `bson:"cardholder,omitempty" json:"cardholder,omitempty"`
	CardholderSurname string `bson:"cardholderSurname,omitempty" json:"cardholderSurname,omitempty"`
	ExpMonth          uint32 `bson:"expMonth,omitempty" json:"expMonth,omitempty"`
	ExpYear           uint32 `bson:"expYear,omitempty" json:"expYear,omitempty"`
	Country           string `bson:"country,omitempty" json:"country,omitempty"`
	MaskedPan         string `bson:"maskedPan,omitempty" json:"maskedPan,omitempty"`
}

// CardPayout stores gateway payout tracking info.
type CardPayout struct {
	PayoutRef         string `bson:"payoutRef,omitempty" json:"payoutRef,omitempty"`
	ProviderPaymentID string `bson:"providerPaymentId,omitempty" json:"providerPaymentId,omitempty"`
	Status            string `bson:"status,omitempty" json:"status,omitempty"`
	FailureReason     string `bson:"failureReason,omitempty" json:"failureReason,omitempty"`
	CardCountry       string `bson:"cardCountry,omitempty" json:"cardCountry,omitempty"`
	MaskedPan         string `bson:"maskedPan,omitempty" json:"maskedPan,omitempty"`
	ProviderCode      string `bson:"providerCode,omitempty" json:"providerCode,omitempty"`
	GatewayReference  string `bson:"gatewayReference,omitempty" json:"gatewayReference,omitempty"`
}

// PaymentEndpoint is a polymorphic payment destination/source.
type PaymentEndpoint struct {
	Type          PaymentEndpointType    `bson:"type" json:"type"`
	InstanceID    string                 `bson:"instanceId,omitempty" json:"instanceId,omitempty"`
	Ledger        *LedgerEndpoint        `bson:"ledger,omitempty" json:"ledger,omitempty"`
	ManagedWallet *ManagedWalletEndpoint `bson:"managedWallet,omitempty" json:"managedWallet,omitempty"`
	ExternalChain *ExternalChainEndpoint `bson:"externalChain,omitempty" json:"externalChain,omitempty"`
	Card          *CardEndpoint          `bson:"card,omitempty" json:"card,omitempty"`
	Metadata      map[string]string      `bson:"metadata,omitempty" json:"metadata,omitempty"`
}

// FXIntent captures FX conversion preferences.
type FXIntent struct {
	Pair              *paymenttypes.CurrencyPair `bson:"pair,omitempty" json:"pair,omitempty"`
	Side              paymenttypes.FXSide        `bson:"side,omitempty" json:"side,omitempty"`
	Firm              bool                       `bson:"firm,omitempty" json:"firm,omitempty"`
	TTLMillis         int64                      `bson:"ttlMillis,omitempty" json:"ttlMillis,omitempty"`
	PreferredProvider string                     `bson:"preferredProvider,omitempty" json:"preferredProvider,omitempty"`
	MaxAgeMillis      int32                      `bson:"maxAgeMillis,omitempty" json:"maxAgeMillis,omitempty"`
}

// PaymentIntent models the requested payment operation.
type PaymentIntent struct {
	Ref                string                  `bson:"ref" json:"ref"`
	Kind               PaymentKind             `bson:"kind" json:"kind"`
	Source             PaymentEndpoint         `bson:"source" json:"source"`
	Destination        PaymentEndpoint         `bson:"destination" json:"destination"`
	Amount             *paymenttypes.Money     `bson:"amount" json:"amount"`
	Comment            string                  `bson:"comment,omitempty" json:"comment,omitempty"`
	RequiresFX         bool                    `bson:"requiresFx,omitempty" json:"requiresFx,omitempty"`
	FX                 *FXIntent               `bson:"fx,omitempty" json:"fx,omitempty"`
	FeePolicy          *paymenttypes.FeePolicy `bson:"feePolicy,omitempty" json:"feePolicy,omitempty"`
	SettlementMode     SettlementMode          `bson:"settlementMode,omitempty" json:"settlementMode,omitempty"`
	FeeTreatment       FeeTreatment            `bson:"feeTreatment,omitempty" json:"feeTreatment,omitempty"`
	SettlementCurrency string                  `bson:"settlementCurrency,omitempty" json:"settlementCurrency,omitempty"`
	Attributes         map[string]string       `bson:"attributes,omitempty" json:"attributes,omitempty"`
	Customer           *Customer               `bson:"customer,omitempty" json:"customer,omitempty"`
}

// Customer captures payer/recipient identity details for downstream processing.
type Customer struct {
	ID         string `bson:"id,omitempty" json:"id,omitempty"`
	FirstName  string `bson:"firstName,omitempty" json:"firstName,omitempty"`
	MiddleName string `bson:"middleName,omitempty" json:"middleName,omitempty"`
	LastName   string `bson:"lastName,omitempty" json:"lastName,omitempty"`
	IP         string `bson:"ip,omitempty" json:"ip,omitempty"`
	Zip        string `bson:"zip,omitempty" json:"zip,omitempty"`
	Country    string `bson:"country,omitempty" json:"country,omitempty"`
	State      string `bson:"state,omitempty" json:"state,omitempty"`
	City       string `bson:"city,omitempty" json:"city,omitempty"`
	Address    string `bson:"address,omitempty" json:"address,omitempty"`
}

// PaymentQuoteSnapshot stores the latest quote info.
type PaymentQuoteSnapshot struct {
	DebitAmount              *paymenttypes.Money                    `bson:"debitAmount,omitempty" json:"debitAmount,omitempty"`
	DebitSettlementAmount    *paymenttypes.Money                    `bson:"debitSettlementAmount,omitempty" json:"debitSettlementAmount,omitempty"`
	ExpectedSettlementAmount *paymenttypes.Money                    `bson:"expectedSettlementAmount,omitempty" json:"expectedSettlementAmount,omitempty"`
	ExpectedFeeTotal         *paymenttypes.Money                    `bson:"expectedFeeTotal,omitempty" json:"expectedFeeTotal,omitempty"`
	TotalCost                *paymenttypes.Money                    `bson:"totalCost,omitempty" json:"totalCost,omitempty"`
	FeeLines                 []*paymenttypes.FeeLine                `bson:"feeLines,omitempty" json:"feeLines,omitempty"`
	FeeRules                 []*paymenttypes.AppliedRule            `bson:"feeRules,omitempty" json:"feeRules,omitempty"`
	Route                    *paymenttypes.QuoteRouteSpecification  `bson:"route,omitempty" json:"route,omitempty"`
	ExecutionConditions      *paymenttypes.QuoteExecutionConditions `bson:"executionConditions,omitempty" json:"executionConditions,omitempty"`
	FXQuote                  *paymenttypes.FXQuote                  `bson:"fxQuote,omitempty" json:"fxQuote,omitempty"`
	NetworkFee               *paymenttypes.NetworkFeeEstimate       `bson:"networkFee,omitempty" json:"networkFee,omitempty"`
	QuoteRef                 string                                 `bson:"quoteRef,omitempty" json:"quoteRef,omitempty"`
}

// ExecutionRefs links to downstream systems.
type ExecutionRefs struct {
	DebitEntryRef    string `bson:"debitEntryRef,omitempty" json:"debitEntryRef,omitempty"`
	CreditEntryRef   string `bson:"creditEntryRef,omitempty" json:"creditEntryRef,omitempty"`
	FXEntryRef       string `bson:"fxEntryRef,omitempty" json:"fxEntryRef,omitempty"`
	ChainTransferRef string `bson:"chainTransferRef,omitempty" json:"chainTransferRef,omitempty"`
	CardPayoutRef    string `bson:"cardPayoutRef,omitempty" json:"cardPayoutRef,omitempty"`
	FeeTransferRef   string `bson:"feeTransferRef,omitempty" json:"feeTransferRef,omitempty"`
}

// PaymentStep is an explicit action within a payment plan.
type PaymentStep struct {
	StepID           string                    `bson:"stepId,omitempty" json:"stepId,omitempty"`
	Rail             Rail                      `bson:"rail" json:"rail"`
	GatewayID        string                    `bson:"gatewayId,omitempty" json:"gatewayId,omitempty"`
	InstanceID       string                    `bson:"instanceId,omitempty" json:"instanceId,omitempty"`
	GatewayInvokeURI string                    `bson:"gatewayInvokeUri,omitempty" json:"gatewayInvokeUri,omitempty"`
	Action           RailOperation             `bson:"action" json:"action"`
	ReportVisibility ReportVisibility          `bson:"reportVisibility,omitempty" json:"reportVisibility,omitempty"`
	DependsOn        []string                  `bson:"dependsOn,omitempty" json:"dependsOn,omitempty"`
	CommitPolicy     CommitPolicy              `bson:"commitPolicy,omitempty" json:"commitPolicy,omitempty"`
	CommitAfter      []string                  `bson:"commitAfter,omitempty" json:"commitAfter,omitempty"`
	Amount           *paymenttypes.Money       `bson:"amount,omitempty" json:"amount,omitempty"`
	FromRole         *account_role.AccountRole `bson:"fromRole,omitempty" json:"fromRole,omitempty"`
	ToRole           *account_role.AccountRole `bson:"toRole,omitempty" json:"toRole,omitempty"`
}

// PaymentPlan captures the ordered list of steps to execute a payment.
type PaymentPlan struct {
	ID             string                  `bson:"id,omitempty" json:"id,omitempty"`
	FXQuote        *paymenttypes.FXQuote   `bson:"fxQuote,omitempty" json:"fxQuote,omitempty"`
	Fees           []*paymenttypes.FeeLine `bson:"fees,omitempty" json:"fees,omitempty"`
	Steps          []*PaymentStep          `bson:"steps,omitempty" json:"steps,omitempty"`
	IdempotencyKey string                  `bson:"idempotencyKey,omitempty" json:"idempotencyKey,omitempty"`
	CreatedAt      time.Time               `bson:"createdAt,omitempty" json:"createdAt,omitempty"`
}

// ExecutionStep describes a planned or executed payment step for reporting.
type ExecutionStep struct {
	Code             string              `bson:"code,omitempty" json:"code,omitempty"`
	Description      string              `bson:"description,omitempty" json:"description,omitempty"`
	Amount           *paymenttypes.Money `bson:"amount,omitempty" json:"amount,omitempty"`
	NetworkFee       *paymenttypes.Money `bson:"networkFee,omitempty" json:"networkFee,omitempty"`
	SourceWalletRef  string              `bson:"sourceWalletRef,omitempty" json:"sourceWalletRef,omitempty"`
	DestinationRef   string              `bson:"destinationRef,omitempty" json:"destinationRef,omitempty"`
	TransferRef      string              `bson:"transferRef,omitempty" json:"transferRef,omitempty"`
	OperationRef     string              `bson:"operationRef,omitempty" json:"operationRef,omitempty"`
	ReportVisibility ReportVisibility    `bson:"reportVisibility,omitempty" json:"reportVisibility,omitempty"`
	Error            string              `bson:"error,omitempty" json:"error,omitempty"`
	State            OperationState      `bson:"state,omitempty" json:"state,omitempty"`
	Metadata         map[string]string   `bson:"metadata,omitempty" json:"metadata,omitempty"`
}

func (s *ExecutionStep) IsTerminal() bool {
	if s.State == OperationStateSuccess ||
		s.State == OperationStateFailed ||
		s.State == OperationStateCancelled ||
		s.State == OperationStateSkipped {
		return true
	}
	return false
}

func (s *ExecutionStep) IsSuccess() bool {
	return s.State == OperationStateSuccess
}

func (s *ExecutionStep) IsFailed() bool {
	return s.State == OperationStateFailed
}

func (s *ExecutionStep) ReadyForNext() bool {
	switch s.State {
	case OperationStateSuccess,
		OperationStateSkipped:
		return true

	default:
		return false
	}
}

// ExecutionPlan captures the ordered list of steps to execute a payment.
type ExecutionPlan struct {
	Steps           []*ExecutionStep    `bson:"steps,omitempty" json:"steps,omitempty"`
	TotalNetworkFee *paymenttypes.Money `bson:"totalNetworkFee,omitempty" json:"totalNetworkFee,omitempty"`
}

// Payment persists orchestrated payment lifecycle.
type Payment struct {
	storable.Base               `bson:",inline" json:",inline"`
	model.OrganizationBoundBase `bson:",inline" json:",inline"`

	PaymentRef     string                `bson:"paymentRef" json:"paymentRef"`
	IdempotencyKey string                `bson:"idempotencyKey" json:"idempotencyKey"`
	Intent         PaymentIntent         `bson:"intent" json:"intent"`
	State          PaymentState          `bson:"state" json:"state"`
	FailureCode    PaymentFailureCode    `bson:"failureCode,omitempty" json:"failureCode,omitempty"`
	FailureReason  string                `bson:"failureReason,omitempty" json:"failureReason,omitempty"`
	LastQuote      *PaymentQuoteSnapshot `bson:"lastQuote,omitempty" json:"lastQuote,omitempty"`
	Execution      *ExecutionRefs        `bson:"execution,omitempty" json:"execution,omitempty"`
	ExecutionPlan  *ExecutionPlan        `bson:"executionPlan,omitempty" json:"executionPlan,omitempty"`
	PaymentPlan    *PaymentPlan          `bson:"paymentPlan,omitempty" json:"paymentPlan,omitempty"`
	Metadata       map[string]string     `bson:"metadata,omitempty" json:"metadata,omitempty"`
	CardPayout     *CardPayout           `bson:"cardPayout,omitempty" json:"cardPayout,omitempty"`
}

// Collection implements storable.Storable.
func (*Payment) Collection() string {
	return mservice.Payments
}

// PaymentFilter enables filtered queries.
type PaymentFilter struct {
	States          []PaymentState
	SourceRef       string
	OrganizationRef string
	DestinationRef  string
	Cursor          string
	Limit           int32
}

// PaymentList contains paginated results.
type PaymentList struct {
	Items      []*Payment
	NextCursor string
}

// Normalize harmonises string fields for indexing and comparisons.
func (p *Payment) Normalize() {
	p.PaymentRef = strings.TrimSpace(p.PaymentRef)
	p.IdempotencyKey = strings.TrimSpace(p.IdempotencyKey)
	p.FailureReason = strings.TrimSpace(p.FailureReason)
	if p.Metadata != nil {
		for k, v := range p.Metadata {
			p.Metadata[k] = strings.TrimSpace(v)
		}
	}
	normalizeEndpoint(&p.Intent.Source)
	normalizeEndpoint(&p.Intent.Destination)
	if p.Intent.Attributes != nil {
		for k, v := range p.Intent.Attributes {
			p.Intent.Attributes[k] = strings.TrimSpace(v)
		}
	}
	p.Intent.Comment = strings.TrimSpace(p.Intent.Comment)
	p.Intent.FeeTreatment = FeeTreatment(strings.TrimSpace(string(p.Intent.FeeTreatment)))
	p.Intent.SettlementCurrency = strings.TrimSpace(p.Intent.SettlementCurrency)
	if p.Intent.Customer != nil {
		p.Intent.Customer.ID = strings.TrimSpace(p.Intent.Customer.ID)
		p.Intent.Customer.FirstName = strings.TrimSpace(p.Intent.Customer.FirstName)
		p.Intent.Customer.MiddleName = strings.TrimSpace(p.Intent.Customer.MiddleName)
		p.Intent.Customer.LastName = strings.TrimSpace(p.Intent.Customer.LastName)
		p.Intent.Customer.IP = strings.TrimSpace(p.Intent.Customer.IP)
		p.Intent.Customer.Zip = strings.TrimSpace(p.Intent.Customer.Zip)
		p.Intent.Customer.Country = strings.TrimSpace(p.Intent.Customer.Country)
		p.Intent.Customer.State = strings.TrimSpace(p.Intent.Customer.State)
		p.Intent.Customer.City = strings.TrimSpace(p.Intent.Customer.City)
		p.Intent.Customer.Address = strings.TrimSpace(p.Intent.Customer.Address)
	}
	if p.Execution != nil {
		p.Execution.DebitEntryRef = strings.TrimSpace(p.Execution.DebitEntryRef)
		p.Execution.CreditEntryRef = strings.TrimSpace(p.Execution.CreditEntryRef)
		p.Execution.FXEntryRef = strings.TrimSpace(p.Execution.FXEntryRef)
		p.Execution.ChainTransferRef = strings.TrimSpace(p.Execution.ChainTransferRef)
	}
	if p.ExecutionPlan != nil {
		for _, step := range p.ExecutionPlan.Steps {
			if step == nil {
				continue
			}
			step.Code = strings.TrimSpace(step.Code)
			step.Description = strings.TrimSpace(step.Description)
			step.SourceWalletRef = strings.TrimSpace(step.SourceWalletRef)
			step.DestinationRef = strings.TrimSpace(step.DestinationRef)
			step.TransferRef = strings.TrimSpace(step.TransferRef)
			step.ReportVisibility = NormalizeReportVisibility(step.ReportVisibility)
			if step.Metadata != nil {
				for k, v := range step.Metadata {
					step.Metadata[k] = strings.TrimSpace(v)
				}
			}
		}
	}
	if p.PaymentPlan != nil {
		p.PaymentPlan.ID = strings.TrimSpace(p.PaymentPlan.ID)
		p.PaymentPlan.IdempotencyKey = strings.TrimSpace(p.PaymentPlan.IdempotencyKey)
		for _, step := range p.PaymentPlan.Steps {
			if step == nil {
				continue
			}
			step.StepID = strings.TrimSpace(step.StepID)
			step.Rail = Rail(strings.TrimSpace(string(step.Rail)))
			step.GatewayID = strings.TrimSpace(step.GatewayID)
			step.InstanceID = strings.TrimSpace(step.InstanceID)
			step.GatewayInvokeURI = strings.TrimSpace(step.GatewayInvokeURI)
			step.Action = RailOperation(strings.TrimSpace(string(step.Action)))
			step.ReportVisibility = NormalizeReportVisibility(step.ReportVisibility)
			step.CommitPolicy = normalizeCommitPolicy(step.CommitPolicy)
			step.DependsOn = normalizeStringList(step.DependsOn)
			step.CommitAfter = normalizeStringList(step.CommitAfter)
		}
	}
}

func normalizeEndpoint(ep *PaymentEndpoint) {
	if ep == nil {
		return
	}
	ep.InstanceID = strings.TrimSpace(ep.InstanceID)
	if ep.Metadata != nil {
		for k, v := range ep.Metadata {
			ep.Metadata[k] = strings.TrimSpace(v)
		}
	}
	switch ep.Type {
	case EndpointTypeLedger:
		if ep.Ledger != nil {
			ep.Ledger.LedgerAccountRef = strings.TrimSpace(ep.Ledger.LedgerAccountRef)
			ep.Ledger.ContraLedgerAccountRef = strings.TrimSpace(ep.Ledger.ContraLedgerAccountRef)
		}
	case EndpointTypeManagedWallet:
		if ep.ManagedWallet != nil {
			ep.ManagedWallet.ManagedWalletRef = strings.TrimSpace(ep.ManagedWallet.ManagedWalletRef)
			if ep.ManagedWallet.Asset != nil {
				ep.ManagedWallet.Asset.Chain = strings.TrimSpace(strings.ToUpper(ep.ManagedWallet.Asset.Chain))
				ep.ManagedWallet.Asset.TokenSymbol = strings.TrimSpace(strings.ToUpper(ep.ManagedWallet.Asset.TokenSymbol))
				ep.ManagedWallet.Asset.ContractAddress = strings.TrimSpace(strings.ToLower(ep.ManagedWallet.Asset.ContractAddress))
			}
		}
	case EndpointTypeExternalChain:
		if ep.ExternalChain != nil {
			ep.ExternalChain.Address = strings.TrimSpace(strings.ToLower(ep.ExternalChain.Address))
			ep.ExternalChain.Memo = strings.TrimSpace(ep.ExternalChain.Memo)
			if ep.ExternalChain.Asset != nil {
				ep.ExternalChain.Asset.Chain = strings.TrimSpace(strings.ToUpper(ep.ExternalChain.Asset.Chain))
				ep.ExternalChain.Asset.TokenSymbol = strings.TrimSpace(strings.ToUpper(ep.ExternalChain.Asset.TokenSymbol))
				ep.ExternalChain.Asset.ContractAddress = strings.TrimSpace(strings.ToLower(ep.ExternalChain.Asset.ContractAddress))
			}
		}
	case EndpointTypeCard:
		if ep.Card != nil {
			ep.Card.Pan = strings.TrimSpace(ep.Card.Pan)
			ep.Card.Token = strings.TrimSpace(ep.Card.Token)
			ep.Card.Cardholder = strings.TrimSpace(ep.Card.Cardholder)
			ep.Card.CardholderSurname = strings.TrimSpace(ep.Card.CardholderSurname)
			ep.Card.Country = strings.TrimSpace(ep.Card.Country)
			ep.Card.MaskedPan = strings.TrimSpace(ep.Card.MaskedPan)
		}
	}
}

func normalizeCommitPolicy(policy CommitPolicy) CommitPolicy {
	val := strings.ToUpper(strings.TrimSpace(string(policy)))
	switch CommitPolicy(val) {
	case CommitPolicyImmediate, CommitPolicyAfterSuccess, CommitPolicyAfterFailure, CommitPolicyAfterCanceled:
		return CommitPolicy(val)
	default:
		if val == "" {
			return CommitPolicyUnspecified
		}
		return CommitPolicy(val)
	}
}

func normalizeStringList(items []string) []string {
	if len(items) == 0 {
		return nil
	}
	result := make([]string, 0, len(items))
	for _, item := range items {
		clean := strings.TrimSpace(item)
		if clean == "" {
			continue
		}
		result = append(result, clean)
	}
	if len(result) == 0 {
		return nil
	}
	return result
}
