package quote_response_mapper_v2

import (
	"strings"
	"time"

	payecon "github.com/tech/sendico/pkg/payments/economics"
	paymentv1 "github.com/tech/sendico/pkg/proto/common/payment/v1"
	storablev1 "github.com/tech/sendico/pkg/proto/common/storable/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"
)

type QuoteResponseMapperV2 struct{}

func New() *QuoteResponseMapperV2 {
	return &QuoteResponseMapperV2{}
}

func (m *QuoteResponseMapperV2) Map(in MapInput) (*MapOutput, error) {
	decision, err := validateStatusInvariants(in.Status)
	if err != nil {
		return nil, err
	}
	expiresAt := normalizeQuoteExpiresAt(in.Quote.ExpiresAt, in.Quote.FXQuote)
	settlementMode := normalizeResolvedSettlementMode(in.Quote.ResolvedSettlementMode)
	feeTreatment := normalizeResolvedFeeTreatment(in.Quote.ResolvedFeeTreatment)

	result := &quotationv2.PaymentQuote{
		Storable:                mapStorable(in.Meta),
		State:                   decision.state,
		BlockReason:             decision.blockReason,
		TransferPrincipalAmount: cloneMoney(in.Quote.TransferPrincipalAmount),
		DestinationAmount:       cloneMoney(in.Quote.DestinationAmount),
		PayerTotalDebitAmount:   cloneMoney(in.Quote.PayerTotalDebitAmount),
		FeeLines:                cloneFeeLines(in.Quote.FeeLines),
		FeeRules:                cloneFeeRules(in.Quote.FeeRules),
		FxQuote:                 cloneFXQuote(in.Quote.FXQuote),
		Route:                   cloneRoute(in.Quote.Route),
		ExecutionConditions:     cloneExecutionConditions(in.Quote.Conditions),
		QuoteRef:                strings.TrimSpace(in.Quote.QuoteRef),
		ExpiresAt:               tsOrNil(expiresAt),
		PricedAt:                tsOrNil(in.Quote.PricedAt),
		ResolvedSettlementMode:  settlementMode,
		ResolvedFeeTreatment:    feeTreatment,
	}

	return &MapOutput{
		Quote:       result,
		State:       decision.state,
		BlockReason: decision.blockReason,
	}, nil
}

func mapStorable(meta QuoteMeta) *storablev1.Storable {
	id := strings.TrimSpace(meta.ID)
	if id == "" && meta.CreatedAt.IsZero() && meta.UpdatedAt.IsZero() {
		return nil
	}
	return &storablev1.Storable{
		Id:        id,
		CreatedAt: tsOrNil(meta.CreatedAt),
		UpdatedAt: tsOrNil(meta.UpdatedAt),
	}
}

func tsOrNil(value time.Time) *timestamppb.Timestamp {
	if value.IsZero() {
		return nil
	}
	return timestamppb.New(value)
}

func normalizeQuoteExpiresAt(expiresAt time.Time, fxQuote *oraclev1.Quote) time.Time {
	if expiresAt.IsZero() || fxQuote == nil || fxQuote.GetExpiresAtUnixMs() <= 0 {
		return expiresAt
	}
	fxExpiresAt := time.UnixMilli(fxQuote.GetExpiresAtUnixMs()).UTC()
	if fxExpiresAt.IsZero() {
		return expiresAt
	}
	if expiresAt.After(fxExpiresAt) {
		return fxExpiresAt
	}
	return expiresAt
}

func normalizeResolvedSettlementMode(mode paymentv1.SettlementMode) paymentv1.SettlementMode {
	switch mode {
	case paymentv1.SettlementMode_SETTLEMENT_FIX_SOURCE, paymentv1.SettlementMode_SETTLEMENT_FIX_RECEIVED:
		return mode
	default:
		return paymentv1.SettlementMode_SETTLEMENT_FIX_SOURCE
	}
}

func normalizeResolvedFeeTreatment(feeTreatment quotationv2.FeeTreatment) quotationv2.FeeTreatment {
	if !payecon.IsValidFeeTreatment(feeTreatment) {
		return payecon.DefaultFeeTreatment()
	}
	if feeTreatment == quotationv2.FeeTreatment_FEE_TREATMENT_UNSPECIFIED {
		return payecon.DefaultFeeTreatment()
	}
	return feeTreatment
}
