package oracle

import (
	"strings"
	"time"

	"github.com/tech/sendico/fx/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"
	oraclev1 "github.com/tech/sendico/pkg/proto/oracle/v1"
	"google.golang.org/protobuf/types/known/timestamppb"
)

func buildResponseMeta(meta *oraclev1.RequestMeta) *oraclev1.ResponseMeta {
	resp := &oraclev1.ResponseMeta{}
	if meta == nil {
		return resp
	}

	trace := meta.GetTrace()
	if trace != nil {
		resp.Trace = trace
	}
	return resp
}

func quoteModelToProto(q *model.Quote) *oraclev1.Quote {
	if q == nil {
		return nil
	}

	return &oraclev1.Quote{
		QuoteRef:        q.QuoteRef,
		Pair:            &fxv1.CurrencyPair{Base: q.Pair.Base, Quote: q.Pair.Quote},
		Side:            sideModelToProto(q.Side),
		Price:           decimalStringToProto(q.Price),
		BaseAmount:      moneyModelToProto(&q.BaseAmount),
		QuoteAmount:     moneyModelToProto(&q.QuoteAmount),
		ExpiresAtUnixMs: q.ExpiresAtUnixMs,
		PricedAt:        timestampFromUnixMillis(q.PricedAtUnixMs, q.CreatedAt),
		Provider:        q.Provider,
		RateRef:         q.RateRef,
		Firm:            q.Firm,
	}
}

func moneyModelToProto(m *paymenttypes.Money) *moneyv1.Money {
	if m == nil {
		return nil
	}
	return &moneyv1.Money{Currency: m.Currency, Amount: m.Amount}
}

func sideModelToProto(side model.QuoteSide) fxv1.Side {
	switch side {
	case model.QuoteSideBuyBaseSellQuote:
		return fxv1.Side_BUY_BASE_SELL_QUOTE
	case model.QuoteSideSellBaseBuyQuote:
		return fxv1.Side_SELL_BASE_BUY_QUOTE
	default:
		return fxv1.Side_SIDE_UNSPECIFIED
	}
}

func rateModelToProto(rate *model.RateSnapshot) *oraclev1.RateSnapshot {
	if rate == nil {
		return nil
	}
	return &oraclev1.RateSnapshot{
		Pair:       &fxv1.CurrencyPair{Base: rate.Pair.Base, Quote: rate.Pair.Quote},
		Mid:        decimalStringToProto(rate.Mid),
		Bid:        decimalStringToProto(rate.Bid),
		Ask:        decimalStringToProto(rate.Ask),
		AsofUnixMs: rate.AsOfUnixMs,
		Provider:   rate.Provider,
		RateRef:    rate.RateRef,
		SpreadBps:  decimalStringToProto(rate.SpreadBps),
	}
}

func pairModelToProto(pair *model.Pair) *oraclev1.PairMeta {
	if pair == nil {
		return nil
	}
	return &oraclev1.PairMeta{
		Pair:      &fxv1.CurrencyPair{Base: pair.Pair.Base, Quote: pair.Pair.Quote},
		BaseMeta:  currencySettingsToProto(&pair.BaseMeta),
		QuoteMeta: currencySettingsToProto(&pair.QuoteMeta),
	}
}

func currencySettingsToProto(c *model.CurrencySettings) *moneyv1.CurrencyMeta {
	if c == nil {
		return nil
	}
	return &moneyv1.CurrencyMeta{
		Code:     c.Code,
		Decimals: c.Decimals,
		Rounding: roundingModeToProto(c.Rounding),
	}
}

func roundingModeToProto(mode model.RoundingMode) moneyv1.RoundingMode {
	switch mode {
	case model.RoundingModeHalfUp:
		return moneyv1.RoundingMode_ROUND_HALF_UP
	case model.RoundingModeDown:
		return moneyv1.RoundingMode_ROUND_DOWN
	case model.RoundingModeHalfEven, model.RoundingModeUnspecified:
		return moneyv1.RoundingMode_ROUND_HALF_EVEN
	default:
		return moneyv1.RoundingMode_ROUNDING_MODE_UNSPECIFIED
	}
}

func decimalStringToProto(value string) *moneyv1.Decimal {
	if strings.TrimSpace(value) == "" {
		return nil
	}
	return &moneyv1.Decimal{Value: value}
}

func timestampFromUnixMillis(ms int64, fallback time.Time) *timestamppb.Timestamp {
	if ms > 0 {
		return timestamppb.New(time.UnixMilli(ms).UTC())
	}
	if !fallback.IsZero() {
		return timestamppb.New(fallback.UTC())
	}
	return nil
}
