package model

import (
	"strings"

	"github.com/tech/sendico/pkg/db/storable"
	"github.com/tech/sendico/pkg/model"
	"github.com/tech/sendico/pkg/mservice"
	feesv1 "github.com/tech/sendico/pkg/proto/billing/fees/v1"
	gatewayv1 "github.com/tech/sendico/pkg/proto/chain/gateway/v1"
	fxv1 "github.com/tech/sendico/pkg/proto/common/fx/v1"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
	oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1"
)

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

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

// PaymentState enumerates lifecycle phases.
type PaymentState string

const (
	PaymentStateUnspecified   PaymentState = "unspecified"
	PaymentStateAccepted      PaymentState = "accepted"
	PaymentStateFundsReserved PaymentState = "funds_reserved"
	PaymentStateSubmitted     PaymentState = "submitted"
	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"
)

// 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"
)

// 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            *gatewayv1.Asset `bson:"asset,omitempty" json:"asset,omitempty"`
}

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

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

// FXIntent captures FX conversion preferences.
type FXIntent struct {
	Pair              *fxv1.CurrencyPair `bson:"pair,omitempty" json:"pair,omitempty"`
	Side              fxv1.Side          `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 {
	Kind        PaymentKind             `bson:"kind" json:"kind"`
	Source      PaymentEndpoint         `bson:"source" json:"source"`
	Destination PaymentEndpoint         `bson:"destination" json:"destination"`
	Amount      *moneyv1.Money          `bson:"amount" json:"amount"`
	RequiresFX  bool                    `bson:"requiresFx,omitempty" json:"requiresFx,omitempty"`
	FX          *FXIntent               `bson:"fx,omitempty" json:"fx,omitempty"`
	FeePolicy   *feesv1.PolicyOverrides `bson:"feePolicy,omitempty" json:"feePolicy,omitempty"`
	Attributes  map[string]string       `bson:"attributes,omitempty" json:"attributes,omitempty"`
}

// PaymentQuoteSnapshot stores the latest quote info.
type PaymentQuoteSnapshot struct {
	DebitAmount              *moneyv1.Money                         `bson:"debitAmount,omitempty" json:"debitAmount,omitempty"`
	ExpectedSettlementAmount *moneyv1.Money                         `bson:"expectedSettlementAmount,omitempty" json:"expectedSettlementAmount,omitempty"`
	ExpectedFeeTotal         *moneyv1.Money                         `bson:"expectedFeeTotal,omitempty" json:"expectedFeeTotal,omitempty"`
	FeeLines                 []*feesv1.DerivedPostingLine           `bson:"feeLines,omitempty" json:"feeLines,omitempty"`
	FeeRules                 []*feesv1.AppliedRule                  `bson:"feeRules,omitempty" json:"feeRules,omitempty"`
	FXQuote                  *oraclev1.Quote                        `bson:"fxQuote,omitempty" json:"fxQuote,omitempty"`
	NetworkFee               *gatewayv1.EstimateTransferFeeResponse `bson:"networkFee,omitempty" json:"networkFee,omitempty"`
	FeeQuoteToken            string                                 `bson:"feeQuoteToken,omitempty" json:"feeQuoteToken,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"`
}

// 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"`
	Metadata       map[string]string     `bson:"metadata,omitempty" json:"metadata,omitempty"`
}

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

// PaymentFilter enables filtered queries.
type PaymentFilter struct {
	States         []PaymentState
	SourceRef      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)
		}
	}
	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)
	}
}

func normalizeEndpoint(ep *PaymentEndpoint) {
	if ep == nil {
		return
	}
	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.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.TokenSymbol = strings.TrimSpace(strings.ToUpper(ep.ExternalChain.Asset.TokenSymbol))
				ep.ExternalChain.Asset.ContractAddress = strings.TrimSpace(strings.ToLower(ep.ExternalChain.Asset.ContractAddress))
			}
		}
	}
}
