package quotation_service_v2

import (
	"strings"
	"time"

	"github.com/tech/sendico/payments/quotation/internal/service/quotation/quote_computation_service"
	"github.com/tech/sendico/payments/quotation/internal/service/quotation/quote_persistence_service"
	"github.com/tech/sendico/payments/quotation/internal/service/quotation/quote_response_mapper_v2"
	"github.com/tech/sendico/payments/storage/model"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
	fxv1 "github.com/tech/sendico/pkg/proto/common/fx/v1"
	moneyv1 "github.com/tech/sendico/pkg/proto/common/money/v1"
	paymentv1 "github.com/tech/sendico/pkg/proto/common/payment/v1"
	oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1"
	quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
	"google.golang.org/protobuf/types/known/timestamppb"
)

func statusInputFromStatus(status quote_response_mapper_v2.QuoteStatus) *quote_persistence_service.StatusInput {
	return &quote_persistence_service.StatusInput{
		State:       status.State,
		BlockReason: status.BlockReason,
	}
}

func statusFromStored(input *model.QuoteStatusV2) quote_response_mapper_v2.QuoteStatus {
	if input == nil {
		return quote_response_mapper_v2.QuoteStatus{
			State: quotationv2.QuoteState_QUOTE_STATE_EXECUTABLE,
		}
	}

	status := quote_response_mapper_v2.QuoteStatus{
		State:       quoteStateToProto(input.State),
		BlockReason: quoteBlockReasonToProto(input.BlockReason),
	}
	if status.State == quotationv2.QuoteState_QUOTE_STATE_UNSPECIFIED {
		status.State = quotationv2.QuoteState_QUOTE_STATE_EXECUTABLE
	}
	if status.State != quotationv2.QuoteState_QUOTE_STATE_BLOCKED {
		status.BlockReason = quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED
	}
	return status
}

func quoteSnapshotFromComputed(src *quote_computation_service.ComputedQuote) *model.PaymentQuoteSnapshot {
	if src == nil {
		return nil
	}
	return &model.PaymentQuoteSnapshot{
		DebitAmount:              modelMoneyFromProto(src.DebitAmount),
		ExpectedSettlementAmount: modelMoneyFromProto(src.CreditAmount),
		TotalCost:                modelMoneyFromProto(src.TotalCost),
		FeeLines:                 feeLinesFromProto(src.FeeLines),
		FeeRules:                 feeRulesFromProto(src.FeeRules),
		Route:                    modelRouteFromProto(src.Route),
		ExecutionConditions:      modelExecutionConditionsFromProto(src.ExecutionConditions),
		QuoteRef:                 strings.TrimSpace(src.QuoteRef),
		FXQuote:                  modelFXQuoteFromProto(src.FXQuote),
	}
}

func canonicalFromSnapshot(
	snapshot *model.PaymentQuoteSnapshot,
	expiresAt time.Time,
	pricedAt time.Time,
	fallbackQuoteRef string,
) quote_response_mapper_v2.CanonicalQuote {
	if snapshot == nil {
		return quote_response_mapper_v2.CanonicalQuote{
			QuoteRef:  strings.TrimSpace(fallbackQuoteRef),
			ExpiresAt: expiresAt,
			PricedAt:  pricedAt,
		}
	}
	resolvedSettlementMode := resolvedSettlementModeFromSnapshot(snapshot)
	return quote_response_mapper_v2.CanonicalQuote{
		QuoteRef:                firstNonEmpty(snapshot.QuoteRef, fallbackQuoteRef),
		TransferPrincipalAmount: protoMoneyFromModel(snapshot.DebitAmount),
		DestinationAmount:       protoMoneyFromModel(snapshot.ExpectedSettlementAmount),
		PayerTotalDebitAmount:   protoMoneyFromModel(snapshot.TotalCost),
		FeeLines:                feeLinesToProto(snapshot.FeeLines),
		FeeRules:                feeRulesToProto(snapshot.FeeRules),
		Route:                   protoRouteFromModel(snapshot.Route),
		Conditions:              protoExecutionConditionsFromModel(snapshot.ExecutionConditions),
		FXQuote:                 protoFXQuoteFromModel(snapshot.FXQuote),
		ResolvedSettlementMode:  resolvedSettlementMode,
		ResolvedFeeTreatment:    defaultResolvedFeeTreatment(),
		ExpiresAt:               expiresAt,
		PricedAt:                pricedAt,
	}
}

func resolvedSettlementModeFromSnapshot(snapshot *model.PaymentQuoteSnapshot) paymentv1.SettlementMode {
	if snapshot == nil || snapshot.Route == nil || snapshot.Route.Settlement == nil {
		return paymentv1.SettlementMode_SETTLEMENT_FIX_SOURCE
	}
	switch strings.ToUpper(strings.TrimSpace(snapshot.Route.Settlement.Model)) {
	case "FIX_RECEIVED", "SETTLEMENT_FIX_RECEIVED", "SETTLEMENT_MODE_FIX_RECEIVED":
		return paymentv1.SettlementMode_SETTLEMENT_FIX_RECEIVED
	default:
		return paymentv1.SettlementMode_SETTLEMENT_FIX_SOURCE
	}
}

func modelMoneyFromProto(src *moneyv1.Money) *paymenttypes.Money {
	if src == nil {
		return nil
	}
	return &paymenttypes.Money{
		Amount:   strings.TrimSpace(src.GetAmount()),
		Currency: strings.ToUpper(strings.TrimSpace(src.GetCurrency())),
	}
}

func protoMoneyFromModel(src *paymenttypes.Money) *moneyv1.Money {
	if src == nil {
		return nil
	}
	return &moneyv1.Money{
		Amount:   strings.TrimSpace(src.GetAmount()),
		Currency: strings.ToUpper(strings.TrimSpace(src.GetCurrency())),
	}
}

func modelFXQuoteFromProto(src *oraclev1.Quote) *paymenttypes.FXQuote {
	if src == nil {
		return nil
	}
	modelQuote := &paymenttypes.FXQuote{
		QuoteRef:        strings.TrimSpace(src.GetQuoteRef()),
		Side:            sideFromProto(src.GetSide()),
		Price:           &paymenttypes.Decimal{Value: strings.TrimSpace(src.GetPrice().GetValue())},
		BaseAmount:      modelMoneyFromProto(src.GetBaseAmount()),
		QuoteAmount:     modelMoneyFromProto(src.GetQuoteAmount()),
		ExpiresAtUnixMs: src.GetExpiresAtUnixMs(),
		Provider:        strings.TrimSpace(src.GetProvider()),
		RateRef:         strings.TrimSpace(src.GetRateRef()),
		Firm:            src.GetFirm(),
	}
	if pair := src.GetPair(); pair != nil {
		modelQuote.Pair = &paymenttypes.CurrencyPair{
			Base:  strings.ToUpper(strings.TrimSpace(pair.GetBase())),
			Quote: strings.ToUpper(strings.TrimSpace(pair.GetQuote())),
		}
	}
	if pricedAt := src.GetPricedAt(); pricedAt != nil {
		modelQuote.PricedAtUnixMs = pricedAt.AsTime().UnixMilli()
	}
	return modelQuote
}

func protoFXQuoteFromModel(src *paymenttypes.FXQuote) *oraclev1.Quote {
	if src == nil {
		return nil
	}
	quote := &oraclev1.Quote{
		QuoteRef:        strings.TrimSpace(src.QuoteRef),
		Side:            sideToProto(src.GetSide()),
		Price:           &moneyv1.Decimal{Value: strings.TrimSpace(src.GetPrice().GetValue())},
		BaseAmount:      protoMoneyFromModel(src.GetBaseAmount()),
		QuoteAmount:     protoMoneyFromModel(src.GetQuoteAmount()),
		ExpiresAtUnixMs: src.GetExpiresAtUnixMs(),
		Provider:        strings.TrimSpace(src.GetProvider()),
		RateRef:         strings.TrimSpace(src.GetRateRef()),
		Firm:            src.GetFirm(),
	}
	if pair := src.GetPair(); pair != nil {
		quote.Pair = &fxv1.CurrencyPair{
			Base:  strings.ToUpper(strings.TrimSpace(pair.GetBase())),
			Quote: strings.ToUpper(strings.TrimSpace(pair.GetQuote())),
		}
	}
	if src.GetPricedAtUnixMs() > 0 {
		quote.PricedAt = timestamppb.New(time.UnixMilli(src.GetPricedAtUnixMs()))
	}
	return quote
}

func sideFromProto(side fxv1.Side) paymenttypes.FXSide {
	switch side {
	case fxv1.Side_BUY_BASE_SELL_QUOTE:
		return paymenttypes.FXSideBuyBaseSellQuote
	case fxv1.Side_SELL_BASE_BUY_QUOTE:
		return paymenttypes.FXSideSellBaseBuyQuote
	default:
		return paymenttypes.FXSideUnspecified
	}
}

func sideToProto(side paymenttypes.FXSide) fxv1.Side {
	switch side {
	case paymenttypes.FXSideBuyBaseSellQuote:
		return fxv1.Side_BUY_BASE_SELL_QUOTE
	case paymenttypes.FXSideSellBaseBuyQuote:
		return fxv1.Side_SELL_BASE_BUY_QUOTE
	default:
		return fxv1.Side_SIDE_UNSPECIFIED
	}
}

func quoteStateToProto(state model.QuoteState) quotationv2.QuoteState {
	switch state {
	case model.QuoteStateIndicative:
		return quotationv2.QuoteState_QUOTE_STATE_INDICATIVE
	case model.QuoteStateExecutable:
		return quotationv2.QuoteState_QUOTE_STATE_EXECUTABLE
	case model.QuoteStateBlocked:
		return quotationv2.QuoteState_QUOTE_STATE_BLOCKED
	case model.QuoteStateExpired:
		return quotationv2.QuoteState_QUOTE_STATE_EXPIRED
	default:
		return quotationv2.QuoteState_QUOTE_STATE_UNSPECIFIED
	}
}

func quoteBlockReasonToProto(reason model.QuoteBlockReason) quotationv2.QuoteBlockReason {
	switch reason {
	case model.QuoteBlockReasonRouteUnavailable:
		return quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_ROUTE_UNAVAILABLE
	case model.QuoteBlockReasonLimitBlocked:
		return quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_LIMIT_BLOCKED
	case model.QuoteBlockReasonRiskBlocked:
		return quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_RISK_BLOCKED
	case model.QuoteBlockReasonInsufficientLiquidity:
		return quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_INSUFFICIENT_LIQUIDITY
	case model.QuoteBlockReasonPriceStale:
		return quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_PRICE_STALE
	case model.QuoteBlockReasonAmountTooSmall:
		return quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_AMOUNT_TOO_SMALL
	case model.QuoteBlockReasonAmountTooLarge:
		return quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_AMOUNT_TOO_LARGE
	default:
		return quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED
	}
}
