package quotation

import (
	"context"
	"strings"
	"time"

	"github.com/tech/sendico/payments/quotation/internal/service/quotation/gateway_funding_profile"
	"github.com/tech/sendico/payments/quotation/internal/service/quotation/quotation_service_v2"
	"github.com/tech/sendico/payments/quotation/internal/service/quotation/quote_computation_service"
	"github.com/tech/sendico/payments/quotation/internal/service/quotation/transfer_intent_hydrator"
	"github.com/tech/sendico/payments/storage/model"
	quotestorage "github.com/tech/sendico/payments/storage/quote"
	payecon "github.com/tech/sendico/pkg/payments/economics"
	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"
	sharedv1 "github.com/tech/sendico/pkg/proto/payments/shared/v1"
	"google.golang.org/protobuf/proto"
)

func newQuotationServiceV2(core *Service) *quotation_service_v2.QuotationServiceV2 {
	if core == nil {
		return nil
	}

	return quotation_service_v2.New(quotation_service_v2.Dependencies{
		Logger:      core.logger,
		QuotesStore: quoteStore(core),
		Hydrator:    transfer_intent_hydrator.New(nil),
		Computation: newQuoteComputationService(core),
	})
}

func quoteStore(core *Service) quotestorage.QuotesStore {
	if core == nil || core.storage == nil {
		return nil
	}
	return core.storage.Quotes()
}

func newQuoteComputationService(core *Service) *quote_computation_service.QuoteComputationService {
	opts := make([]quote_computation_service.Option, 0, 3)
	if core != nil && core.storage != nil {
		if routes := core.storage.Routes(); routes != nil {
			opts = append(
				opts,
				quote_computation_service.WithRouteStore(routes),
				quote_computation_service.WithLogger(core.logger),
			)
		}
	}
	if core != nil && core.deps.gatewayRegistry != nil {
		opts = append(opts, quote_computation_service.WithGatewayRegistry(core.deps.gatewayRegistry))
	}
	if resolver := newManagedWalletNetworkResolver(core); resolver != nil {
		opts = append(opts, quote_computation_service.WithManagedWalletNetworkResolver(resolver))
	}
	if resolver := newLedgerAccountCurrencyResolver(core); resolver != nil {
		opts = append(opts, quote_computation_service.WithLedgerAccountCurrencyResolver(resolver))
	}
	if resolver := fundingProfileResolver(core); resolver != nil {
		opts = append(opts, quote_computation_service.WithFundingProfileResolver(resolver))
	}
	return quote_computation_service.New(legacyQuoteComputationCore{core: core}, opts...)
}

func fundingProfileResolver(core *Service) gateway_funding_profile.FundingProfileResolver {
	if core == nil {
		return nil
	}

	cardRoutes := make(map[string]gateway_funding_profile.CardGatewayFundingRoute, len(core.deps.cardRoutes))
	for key, route := range core.deps.cardRoutes {
		routeKey := strings.TrimSpace(key)
		if routeKey == "" {
			continue
		}
		cardRoutes[routeKey] = gateway_funding_profile.CardGatewayFundingRoute{
			FundingAddress: strings.TrimSpace(route.FundingAddress),
			FeeAddress:     strings.TrimSpace(route.FeeAddress),
			FeeWalletRef:   strings.TrimSpace(route.FeeWalletRef),
		}
	}

	feeLedgerAccounts := make(map[string]string, len(core.deps.feeLedgerAccounts))
	for key, accountRef := range core.deps.feeLedgerAccounts {
		accountKey := strings.TrimSpace(key)
		account := strings.TrimSpace(accountRef)
		if accountKey == "" || account == "" {
			continue
		}
		feeLedgerAccounts[accountKey] = account
	}

	if len(cardRoutes) == 0 && len(feeLedgerAccounts) == 0 {
		return nil
	}
	return gateway_funding_profile.NewStaticFundingProfileResolver(gateway_funding_profile.StaticFundingProfileResolverInput{
		DefaultCardGateway: defaultCardGateway,
		DefaultMode:        model.FundingModeNone,
		CardRoutes:         cardRoutes,
		FeeLedgerAccounts:  feeLedgerAccounts,
	})
}

type legacyQuoteComputationCore struct {
	core *Service
}

func (c legacyQuoteComputationCore) BuildQuote(ctx context.Context, in quote_computation_service.BuildQuoteInput) (*quote_computation_service.ComputedQuote, time.Time, error) {
	if c.core == nil {
		return nil, time.Time{}, errStorageUnavailable
	}

	request := &quoteRequest{
		Meta: &sharedv1.RequestMeta{
			OrganizationRef: strings.TrimSpace(in.OrganizationRef),
		},
		IdempotencyKey: strings.TrimSpace(in.IdempotencyKey),
		Intent:         protoIntentFromModel(in.Intent),
	}

	legacyQuote, expiresAt, err := c.core.buildPaymentQuote(ctx, strings.TrimSpace(in.OrganizationRef), request)
	if err != nil {
		return nil, time.Time{}, err
	}
	return mapLegacyQuote(in, legacyQuote), expiresAt, nil
}

func mapLegacyQuote(in quote_computation_service.BuildQuoteInput, src *sharedv1.PaymentQuote) *quote_computation_service.ComputedQuote {
	if src == nil {
		return &quote_computation_service.ComputedQuote{}
	}
	resolvedSettlementMode := settlementModeToProto(in.Intent.SettlementMode)
	if resolvedSettlementMode == paymentv1.SettlementMode_SETTLEMENT_UNSPECIFIED {
		resolvedSettlementMode = paymentv1.SettlementMode_SETTLEMENT_FIX_SOURCE
	}
	resolvedFeeTreatment := feeTreatmentToProto(in.Intent.FeeTreatment)
	if resolvedFeeTreatment == quotationv2.FeeTreatment_FEE_TREATMENT_UNSPECIFIED {
		resolvedFeeTreatment = payecon.DefaultFeeTreatment()
	}
	return &quote_computation_service.ComputedQuote{
		DebitAmount:            cloneProtoMoney(src.GetDebitSettlementAmount()),
		CreditAmount:           cloneProtoMoney(src.GetExpectedSettlementAmount()),
		TotalCost:              cloneProtoMoney(src.GetDebitAmount()),
		FeeLines:               cloneFeeLines(src.GetFeeLines()),
		FeeRules:               cloneFeeRules(src.GetFeeRules()),
		FXQuote:                cloneFXQuote(src.GetFxQuote()),
		Route:                  cloneRouteSpecification(in.Route),
		ExecutionConditions:    cloneExecutionConditions(in.ExecutionConditions),
		ResolvedSettlementMode: resolvedSettlementMode,
		ResolvedFeeTreatment:   resolvedFeeTreatment,
	}
}

func feeTreatmentToProto(value model.FeeTreatment) quotationv2.FeeTreatment {
	switch value {
	case model.FeeTreatmentDeductFromDestination:
		return quotationv2.FeeTreatment_FEE_TREATMENT_DEDUCT_FROM_DESTINATION
	case model.FeeTreatmentAddToSource:
		return quotationv2.FeeTreatment_FEE_TREATMENT_ADD_TO_SOURCE
	default:
		return quotationv2.FeeTreatment_FEE_TREATMENT_UNSPECIFIED
	}
}

func cloneRouteSpecification(src *quotationv2.RouteSpecification) *quotationv2.RouteSpecification {
	if src == nil {
		return nil
	}
	cloned, ok := proto.Clone(src).(*quotationv2.RouteSpecification)
	if !ok {
		return nil
	}
	return cloned
}

func cloneExecutionConditions(src *quotationv2.ExecutionConditions) *quotationv2.ExecutionConditions {
	if src == nil {
		return nil
	}
	cloned, ok := proto.Clone(src).(*quotationv2.ExecutionConditions)
	if !ok {
		return nil
	}
	return cloned
}

func cloneFXQuote(src *oraclev1.Quote) *oraclev1.Quote {
	if src == nil {
		return nil
	}
	cloned, ok := proto.Clone(src).(*oraclev1.Quote)
	if !ok {
		return nil
	}
	return cloned
}
