package quote_computation_service

import (
	"strings"

	"github.com/tech/sendico/payments/quotation/internal/service/quotation/transfer_intent_hydrator"
	"github.com/tech/sendico/payments/storage/model"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
)

type fxDerivationResult struct {
	InferredSourceCurrency       string
	InferredDestinationCurrency  string
	EffectiveSourceCurrency      string
	EffectiveDestinationCurrency string
	ExplicitOverrideApplied      bool
	RequiresFXInferred           bool
}

func modelIntentFromQuoteIntent(src *transfer_intent_hydrator.QuoteIntent) model.PaymentIntent {
	if src == nil {
		return model.PaymentIntent{}
	}
	settlementCurrency := strings.ToUpper(strings.TrimSpace(src.SettlementCurrency))
	if settlementCurrency == "" && src.Amount != nil {
		settlementCurrency = strings.ToUpper(strings.TrimSpace(src.Amount.Currency))
	}

	return model.PaymentIntent{
		Ref:                strings.TrimSpace(src.Ref),
		Kind:               modelPaymentKind(src.Kind, src.Destination),
		Source:             modelEndpointFromQuoteEndpoint(src.Source),
		Destination:        modelEndpointFromQuoteEndpoint(src.Destination),
		Amount:             cloneModelMoney(src.Amount),
		Comment:            strings.TrimSpace(src.Comment),
		FX:                 fxIntentFromHydratedIntent(src),
		RequiresFX:         src.RequiresFX,
		Attributes:         cloneStringMap(src.Attributes),
		SettlementMode:     modelSettlementMode(src.SettlementMode),
		FeeTreatment:       modelFeeTreatment(src.FeeTreatment),
		SettlementCurrency: settlementCurrency,
	}
}

func fxIntentFromHydratedIntent(src *transfer_intent_hydrator.QuoteIntent) *model.FXIntent {
	if src == nil || src.FX == nil {
		return nil
	}
	result := &model.FXIntent{
		Side:              src.FX.Side,
		Firm:              src.FX.Firm,
		TTLMillis:         src.FX.TTLms,
		PreferredProvider: strings.TrimSpace(src.FX.PreferredProvider),
		MaxAgeMillis:      src.FX.MaxAgeMs,
	}
	if src.FX.Pair != nil {
		result.Pair = &paymenttypes.CurrencyPair{
			Base:  normalizeAsset(src.FX.Pair.GetBase()),
			Quote: normalizeAsset(src.FX.Pair.GetQuote()),
		}
	}
	if result.Side == paymenttypes.FXSideUnspecified &&
		result.Pair == nil &&
		!result.Firm &&
		result.TTLMillis == 0 &&
		result.PreferredProvider == "" &&
		result.MaxAgeMillis == 0 {
		return nil
	}
	return result
}

func ensureDerivedFXIntent(
	intent *model.PaymentIntent,
	inferredSourceCurrency string,
	inferredDestinationCurrency string,
) fxDerivationResult {
	result := fxDerivationResult{
		InferredSourceCurrency:      normalizeAsset(inferredSourceCurrency),
		InferredDestinationCurrency: normalizeAsset(inferredDestinationCurrency),
	}
	if intent == nil {
		return result
	}

	amountCurrency := ""
	if intent.Amount != nil {
		amountCurrency = normalizeAsset(intent.Amount.GetCurrency())
	}
	settlementCurrency := normalizeAsset(intent.SettlementCurrency)
	if result.InferredDestinationCurrency != "" {
		settlementCurrency = result.InferredDestinationCurrency
	}
	if settlementCurrency == "" {
		settlementCurrency = amountCurrency
	}
	if intent.SettlementCurrency == "" && settlementCurrency != "" {
		intent.SettlementCurrency = settlementCurrency
	}

	sourceCurrency := firstNonEmpty(result.InferredSourceCurrency, sourceAssetToken(intent.Source))
	sourceCurrencyBeforeExplicit := sourceCurrency
	settlementCurrencyBeforeExplicit := settlementCurrency
	requiresFXBeforeExplicit := intent.RequiresFX

	explicitSourceCurrency, explicitDestinationCurrency := fxTradeCurrencies(intent.FX)
	if explicitSourceCurrency != "" && explicitDestinationCurrency != "" {
		sourceCurrency = explicitSourceCurrency
		settlementCurrency = explicitDestinationCurrency
		intent.SettlementCurrency = settlementCurrency
		intent.RequiresFX = true
		if sourceCurrencyBeforeExplicit == "" ||
			settlementCurrencyBeforeExplicit == "" ||
			!strings.EqualFold(sourceCurrencyBeforeExplicit, explicitSourceCurrency) ||
			!strings.EqualFold(settlementCurrencyBeforeExplicit, explicitDestinationCurrency) ||
			!requiresFXBeforeExplicit {
			result.ExplicitOverrideApplied = true
		}
	} else if !intent.RequiresFX &&
		sourceCurrency != "" &&
		settlementCurrency != "" &&
		!strings.EqualFold(sourceCurrency, settlementCurrency) {
		intent.RequiresFX = true
		result.RequiresFXInferred = true
	}

	if !intent.RequiresFX {
		result.EffectiveSourceCurrency = sourceCurrency
		result.EffectiveDestinationCurrency = settlementCurrency
		return result
	}

	baseCurrency := firstNonEmpty(sourceCurrency, amountCurrency)
	quoteCurrency := settlementCurrency
	if baseCurrency == "" || quoteCurrency == "" {
		result.EffectiveSourceCurrency = sourceCurrency
		result.EffectiveDestinationCurrency = settlementCurrency
		return result
	}

	if intent.FX == nil {
		intent.FX = &model.FXIntent{}
	}
	if strings.TrimSpace(string(intent.FX.Side)) == "" || intent.FX.Side == paymenttypes.FXSideUnspecified {
		intent.FX.Side = paymenttypes.FXSideSellBaseBuyQuote
	}
	if intent.FX.Pair == nil {
		intent.FX.Pair = &paymenttypes.CurrencyPair{}
	}
	desiredBase, desiredQuote := fxPairFromTradeCurrencies(intent.FX.Side, baseCurrency, quoteCurrency)
	if normalizeAsset(intent.FX.Pair.Base) == "" {
		intent.FX.Pair.Base = desiredBase
	}
	if normalizeAsset(intent.FX.Pair.Quote) == "" {
		intent.FX.Pair.Quote = desiredQuote
	}

	result.EffectiveSourceCurrency, result.EffectiveDestinationCurrency = fxTradeCurrencies(intent.FX)
	if result.EffectiveSourceCurrency == "" {
		result.EffectiveSourceCurrency = sourceCurrency
	}
	if result.EffectiveDestinationCurrency == "" {
		result.EffectiveDestinationCurrency = settlementCurrency
	}
	return result
}

func fxPairFromTradeCurrencies(side paymenttypes.FXSide, sourceCurrency, destinationCurrency string) (string, string) {
	sourceCurrency = normalizeAsset(sourceCurrency)
	destinationCurrency = normalizeAsset(destinationCurrency)
	switch side {
	case paymenttypes.FXSideBuyBaseSellQuote:
		return destinationCurrency, sourceCurrency
	default:
		return sourceCurrency, destinationCurrency
	}
}

func fxTradeCurrencies(fx *model.FXIntent) (string, string) {
	if fx == nil || fx.Pair == nil {
		return "", ""
	}
	base := normalizeAsset(fx.Pair.GetBase())
	quote := normalizeAsset(fx.Pair.GetQuote())
	if base == "" || quote == "" {
		return "", ""
	}
	switch fx.Side {
	case paymenttypes.FXSideBuyBaseSellQuote:
		return quote, base
	default:
		return base, quote
	}
}

func modelEndpointFromQuoteEndpoint(src transfer_intent_hydrator.QuoteEndpoint) model.PaymentEndpoint {
	result := model.PaymentEndpoint{
		Type: model.EndpointTypeUnspecified,
	}

	switch src.Type {
	case transfer_intent_hydrator.QuoteEndpointTypeLedger:
		result.Type = model.EndpointTypeLedger
		if src.Ledger != nil {
			result.Ledger = &model.LedgerEndpoint{
				LedgerAccountRef:       strings.TrimSpace(src.Ledger.LedgerAccountRef),
				ContraLedgerAccountRef: strings.TrimSpace(src.Ledger.ContraLedgerAccountRef),
			}
		}
	case transfer_intent_hydrator.QuoteEndpointTypeManagedWallet:
		result.Type = model.EndpointTypeManagedWallet
		if src.ManagedWallet != nil {
			result.ManagedWallet = &model.ManagedWalletEndpoint{
				ManagedWalletRef: strings.TrimSpace(src.ManagedWallet.ManagedWalletRef),
				Asset:            cloneAsset(src.ManagedWallet.Asset),
			}
		}
	case transfer_intent_hydrator.QuoteEndpointTypeExternalChain:
		result.Type = model.EndpointTypeExternalChain
		if src.ExternalChain != nil {
			result.ExternalChain = &model.ExternalChainEndpoint{
				Asset:   cloneAsset(src.ExternalChain.Asset),
				Address: strings.TrimSpace(src.ExternalChain.Address),
				Memo:    strings.TrimSpace(src.ExternalChain.Memo),
			}
		}
	case transfer_intent_hydrator.QuoteEndpointTypeCard:
		result.Type = model.EndpointTypeCard
		if src.Card != nil {
			result.Card = &model.CardEndpoint{
				Pan:               strings.TrimSpace(src.Card.Pan),
				Token:             strings.TrimSpace(src.Card.Token),
				Cardholder:        strings.TrimSpace(src.Card.Cardholder),
				CardholderSurname: strings.TrimSpace(src.Card.CardholderSurname),
				ExpMonth:          src.Card.ExpMonth,
				ExpYear:           src.Card.ExpYear,
				Country:           strings.TrimSpace(src.Card.Country),
				MaskedPan:         strings.TrimSpace(src.Card.MaskedPan),
			}
		}
	}

	return result
}

func modelPaymentKind(kind transfer_intent_hydrator.QuoteIntentKind, destination transfer_intent_hydrator.QuoteEndpoint) model.PaymentKind {
	switch kind {
	case transfer_intent_hydrator.QuoteIntentKindPayout:
		return model.PaymentKindPayout
	case transfer_intent_hydrator.QuoteIntentKindInternalTransfer:
		return model.PaymentKindInternalTransfer
	case transfer_intent_hydrator.QuoteIntentKindFXConversion:
		return model.PaymentKindFXConversion
	}
	switch destination.Type {
	case transfer_intent_hydrator.QuoteEndpointTypeExternalChain, transfer_intent_hydrator.QuoteEndpointTypeCard:
		return model.PaymentKindPayout
	default:
		return model.PaymentKindInternalTransfer
	}
}

func modelSettlementMode(mode transfer_intent_hydrator.QuoteSettlementMode) model.SettlementMode {
	switch mode {
	case transfer_intent_hydrator.QuoteSettlementModeFixSource:
		return model.SettlementModeFixSource
	case transfer_intent_hydrator.QuoteSettlementModeFixReceived:
		return model.SettlementModeFixReceived
	default:
		return model.SettlementModeFixSource
	}
}

func modelFeeTreatment(value transfer_intent_hydrator.QuoteFeeTreatment) model.FeeTreatment {
	switch value {
	case transfer_intent_hydrator.QuoteFeeTreatmentDeductFromDestination:
		return model.FeeTreatmentDeductFromDestination
	case transfer_intent_hydrator.QuoteFeeTreatmentAddToSource:
		return model.FeeTreatmentAddToSource
	default:
		return model.FeeTreatmentAddToSource
	}
}
