package quote_computation_service

import (
	"github.com/tech/sendico/pkg/discovery"
	"strings"

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

func validateQuoteRouteBinding(quote *ComputedQuote, input BuildQuoteInput) error {
	if quote == nil {
		return merrors.InvalidArgument("computed quote is required")
	}
	if input.Route == nil {
		return merrors.InvalidArgument("build_quote_input.route is required")
	}
	if quote.Route == nil {
		return merrors.InvalidArgument("computed quote route is required")
	}
	if !sameRouteSpecification(quote.Route, input.Route) {
		return merrors.InvalidArgument("computed quote route must match selected route")
	}
	return nil
}

func sameRouteSpecification(left, right *quotationv2.RouteSpecification) bool {
	if left == nil || right == nil {
		return left == right
	}
	return sameRouteSettlement(left.GetSettlement(), right.GetSettlement()) &&
		sameRouteReference(left.GetRouteRef(), right.GetRouteRef()) &&
		samePricingProfileReference(left.GetPricingProfileRef(), right.GetPricingProfileRef()) &&
		sameRouteHops(left.GetHops(), right.GetHops())
}

func sameRouteSettlement(
	left *quotationv2.RouteSettlement,
	right *quotationv2.RouteSettlement,
) bool {
	leftChain, leftToken, leftContract, leftModel := normalizeSettlementParts(left)
	rightChain, rightToken, rightContract, rightModel := normalizeSettlementParts(right)
	return leftChain == rightChain &&
		leftToken == rightToken &&
		leftContract == rightContract &&
		leftModel == rightModel
}

func normalizeSettlementParts(src *quotationv2.RouteSettlement) (chain, token, contract, model string) {
	if src != nil {
		if asset := src.GetAsset(); asset != nil {
			key := asset.GetKey()
			chain = normalizeAsset(key.GetChain())
			token = normalizeAsset(key.GetTokenSymbol())
			contract = strings.TrimSpace(asset.GetContractAddress())
		}
		model = normalizeSettlementModel(src.GetModel())
	}
	return chain, token, contract, model
}

func normalizeRail(value string) string {
	if rail := model.ParseRail(value); rail != discovery.RailUnspecified {
		return string(rail)
	}
	return strings.ToUpper(strings.TrimSpace(value))
}

func normalizeProvider(value string) string {
	return strings.ToLower(strings.TrimSpace(value))
}

func normalizeAsset(value string) string {
	return strings.ToUpper(strings.TrimSpace(value))
}

func normalizeSettlementModel(value string) string {
	return strings.ToUpper(strings.TrimSpace(value))
}

func normalizeNetwork(value string) string {
	return strings.ToLower(strings.TrimSpace(value))
}

func sameRouteReference(left, right string) bool {
	return strings.TrimSpace(left) == strings.TrimSpace(right)
}

func samePricingProfileReference(left, right string) bool {
	return strings.TrimSpace(left) == strings.TrimSpace(right)
}

func sameRouteHops(left, right []*quotationv2.RouteHop) bool {
	if len(left) != len(right) {
		return false
	}
	for i := range left {
		if !sameRouteHop(left[i], right[i]) {
			return false
		}
	}
	return true
}

func sameRouteHop(left, right *quotationv2.RouteHop) bool {
	if left == nil || right == nil {
		return left == right
	}
	return left.GetIndex() == right.GetIndex() &&
		normalizeRail(left.GetRail()) == normalizeRail(right.GetRail()) &&
		normalizeProvider(left.GetGateway()) == normalizeProvider(right.GetGateway()) &&
		strings.TrimSpace(left.GetInstanceId()) == strings.TrimSpace(right.GetInstanceId()) &&
		normalizeNetwork(left.GetNetwork()) == normalizeNetwork(right.GetNetwork()) &&
		left.GetRole() == right.GetRole()
}
