package quote_computation_service

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"github.com/tech/sendico/pkg/discovery"
	"sort"
	"strings"

	"github.com/tech/sendico/payments/quotation/internal/service/quotation/gateway_funding_profile"
	"github.com/tech/sendico/payments/storage/model"
	quotationv2 "github.com/tech/sendico/pkg/proto/payments/quotation/v2"
)

func buildRouteSpecification(
	intent model.PaymentIntent,
	network string,
	steps []*QuoteComputationStep,
) *quotationv2.RouteSpecification {
	hops := buildRouteHops(steps, network)
	route := &quotationv2.RouteSpecification{
		Settlement: buildRouteSettlement(intent, network, hops),
		Hops:       hops,
	}
	route.RouteRef = buildRouteReference(route)
	route.PricingProfileRef = buildPricingProfileReference(route)
	return route
}

func buildExecutionConditions(
	previewOnly bool,
	steps []*QuoteComputationStep,
	funding *gateway_funding_profile.QuoteFundingGate,
) (*quotationv2.ExecutionConditions, quotationv2.QuoteBlockReason) {
	blockReason := quotationv2.QuoteBlockReason_QUOTE_BLOCK_REASON_UNSPECIFIED
	conditions := &quotationv2.ExecutionConditions{
		Readiness:                         quotationv2.QuoteExecutionReadiness_QUOTE_EXECUTION_READINESS_LIQUIDITY_READY,
		BatchingEligible:                  isBatchingEligible(steps),
		PrefundingRequired:                false,
		PrefundingCostIncluded:            false,
		LiquidityCheckRequiredAtExecution: true,
		LatencyHint:                       "instant",
		Assumptions: []string{
			"execution_time_liquidity_check",
			"execution_time_provider_limits",
		},
	}

	if previewOnly {
		conditions.Readiness = quotationv2.QuoteExecutionReadiness_QUOTE_EXECUTION_READINESS_INDICATIVE
		conditions.LatencyHint = "indicative"
	}

	if funding != nil {
		switch funding.Mode {
		case model.FundingModeBalanceReserve:
			conditions.PrefundingRequired = true
			conditions.LatencyHint = "reserve_before_payout"
		case model.FundingModeDepositObserved:
			conditions.PrefundingRequired = true
			conditions.LatencyHint = "deposit_confirmation_required"
		}
	}

	if !previewOnly && conditions.PrefundingRequired {
		conditions.Readiness = quotationv2.QuoteExecutionReadiness_QUOTE_EXECUTION_READINESS_LIQUIDITY_OBTAINABLE
		conditions.Assumptions = append(conditions.Assumptions, "prefunding_may_be_required_at_execution")
	}

	if !previewOnly && !conditions.PrefundingRequired {
		conditions.Assumptions = append(conditions.Assumptions, "liquidity_expected_available_now")
	}

	return conditions, blockReason
}

func settlementModelString(mode model.SettlementMode) string {
	switch mode {
	case model.SettlementModeFixSource:
		return "fix_source"
	case model.SettlementModeFixReceived:
		return "fix_received"
	default:
		return "fix_source"
	}
}

func isBatchingEligible(steps []*QuoteComputationStep) bool {
	for _, step := range steps {
		if step != nil && step.IncludeInAggregate {
			return true
		}
	}
	return false
}

func buildRouteHops(steps []*QuoteComputationStep, fallbackNetwork string) []*quotationv2.RouteHop {
	filtered := make([]*QuoteComputationStep, 0, len(steps))
	for _, step := range steps {
		if step == nil {
			continue
		}
		filtered = append(filtered, step)
	}
	if len(filtered) == 0 {
		return nil
	}
	result := make([]*quotationv2.RouteHop, 0, len(filtered))
	lastIndex := len(filtered) - 1
	for i, step := range filtered {
		hop := &quotationv2.RouteHop{
			Index:      uint32(i + 1),
			Rail:       normalizeRail(string(step.Rail)),
			Gateway:    normalizeProvider(step.GatewayID),
			InstanceId: strings.TrimSpace(step.InstanceID),
			Network:    normalizeNetwork(firstNonEmpty(fallbackNetwork)),
			Role:       roleForHopIndex(i, lastIndex),
		}
		if hop.Gateway == "" && hop.Rail == normalizeRail(string(discovery.RailLedger)) {
			hop.Gateway = "internal"
		}
		result = append(result, hop)
	}
	return result
}

func roleForHopIndex(index, last int) quotationv2.RouteHopRole {
	switch {
	case index <= 0:
		return quotationv2.RouteHopRole_ROUTE_HOP_ROLE_SOURCE
	case index >= last:
		return quotationv2.RouteHopRole_ROUTE_HOP_ROLE_DESTINATION
	default:
		return quotationv2.RouteHopRole_ROUTE_HOP_ROLE_TRANSIT
	}
}

func buildRouteReference(route *quotationv2.RouteSpecification) string {
	signature := routeTopologySignature(route, true)
	if signature == "" {
		return ""
	}
	sum := sha256.Sum256([]byte(signature))
	return "rte_" + hex.EncodeToString(sum[:12])
}

func buildPricingProfileReference(route *quotationv2.RouteSpecification) string {
	signature := routeTopologySignature(route, false)
	if signature == "" {
		return ""
	}
	sum := sha256.Sum256([]byte(signature))
	return "fee_" + hex.EncodeToString(sum[:10])
}

func routeTopologySignature(route *quotationv2.RouteSpecification, includeInstances bool) string {
	if route == nil {
		return ""
	}
	parts := make([]string, 0, 8)
	if settlement := route.GetSettlement(); settlement != nil {
		if asset := settlement.GetAsset(); asset != nil {
			key := asset.GetKey()
			if chain := normalizeAsset(key.GetChain()); chain != "" {
				parts = append(parts, chain)
			}
			if token := normalizeAsset(key.GetTokenSymbol()); token != "" {
				parts = append(parts, token)
			}
			if contract := strings.TrimSpace(asset.GetContractAddress()); contract != "" {
				parts = append(parts, strings.ToLower(contract))
			}
		}
		if model := normalizeSettlementModel(settlement.GetModel()); model != "" {
			parts = append(parts, model)
		}
	}

	hops := route.GetHops()
	if len(hops) > 0 {
		copied := make([]*quotationv2.RouteHop, 0, len(hops))
		for _, hop := range hops {
			if hop != nil {
				copied = append(copied, hop)
			}
		}
		sort.Slice(copied, func(i, j int) bool {
			return copied[i].GetIndex() < copied[j].GetIndex()
		})
		for _, hop := range copied {
			hopParts := []string{
				fmt.Sprintf("%d", hop.GetIndex()),
				normalizeRail(hop.GetRail()),
				normalizeProvider(hop.GetGateway()),
				normalizeNetwork(hop.GetNetwork()),
				fmt.Sprintf("%d", hop.GetRole()),
			}
			if includeInstances {
				hopParts = append(hopParts, strings.TrimSpace(hop.GetInstanceId()))
			}
			parts = append(parts, strings.Join(hopParts, ":"))
		}
	}
	if len(parts) == 0 {
		return ""
	}
	return strings.Join(parts, "|")
}
