package xplan

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

	"github.com/tech/sendico/payments/storage/model"
	"github.com/tech/sendico/pkg/merrors"
	paymenttypes "github.com/tech/sendico/pkg/payments/types"
)

func internalRailForBoundary(from normalizedHop, to normalizedHop) model.Rail {
	if isInternalRail(from.rail) {
		return from.rail
	}
	if isInternalRail(to.rail) {
		return to.rail
	}
	return discovery.RailLedger
}

func isInternalRail(rail model.Rail) bool {
	return rail == discovery.RailLedger
}

func isExternalRail(rail model.Rail) bool {
	switch rail {
	case discovery.RailCrypto, discovery.RailProviderSettlement, discovery.RailCardPayout, discovery.RailFiatOnRamp:
		return true
	default:
		return false
	}
}

func custodyForRail(rail model.Rail) Custody {
	if isInternalRail(rail) {
		return CustodyInternal
	}
	if isExternalRail(rail) {
		return CustodyExternal
	}
	return CustodyUnspecified
}

func singleHopCode(hop normalizedHop, op string) string {
	return fmt.Sprintf("hop.%d.%s.%s", hop.index, railToken(hop.rail), strings.TrimSpace(op))
}

func edgeCode(from normalizedHop, to normalizedHop, rail model.Rail, op string) string {
	return fmt.Sprintf(
		"edge.%d_%d.%s.%s",
		from.index,
		to.index,
		railToken(rail),
		strings.TrimSpace(op),
	)
}

func railToken(rail model.Rail) string {
	if rail == discovery.RailCardPayout {
		return "card_payout"
	}
	return strings.ToLower(strings.TrimSpace(string(rail)))
}

func observedKey(hop normalizedHop) string {
	return fmt.Sprintf("%d:%d:%s:%s", hop.pos, hop.index, strings.TrimSpace(string(hop.rail)), hop.instanceID)
}

func normalizeRouteHops(route *paymenttypes.QuoteRouteSpecification, intent model.PaymentIntent) ([]normalizedHop, error) {
	if route == nil {
		return nil, merrors.InvalidArgument("quote_snapshot.route is required")
	}

	if len(route.Hops) == 0 {
		rail := normalizeRail(route.Rail)
		if rail == discovery.RailUnspecified {
			return nil, merrors.InvalidArgument("quote_snapshot.route.rail is required")
		}
		return []normalizedHop{
			{
				index:      0,
				rail:       rail,
				gateway:    strings.TrimSpace(route.Provider),
				instanceID: "",
				network:    strings.TrimSpace(route.Network),
				role:       paymenttypes.QuoteRouteHopRoleDestination,
				pos:        0,
			},
		}, nil
	}

	hops := make([]normalizedHop, 0, len(route.Hops))
	for i, hop := range route.Hops {
		if hop == nil {
			continue
		}

		rail := normalizeRail(firstNonEmpty(hop.Rail, route.Rail))
		if rail == discovery.RailUnspecified {
			return nil, merrors.InvalidArgument("quote_snapshot.route.hops[" + itoa(i) + "].rail is required")
		}

		hops = append(hops, normalizedHop{
			index:      hop.Index,
			rail:       rail,
			gateway:    strings.TrimSpace(firstNonEmpty(hop.Gateway, route.Provider)),
			instanceID: strings.TrimSpace(hop.InstanceID),
			network:    strings.TrimSpace(firstNonEmpty(hop.Network, route.Network)),
			role:       normalizeHopRole(hop.Role, i, len(route.Hops), intent),
			pos:        i,
		})
	}

	if len(hops) == 0 {
		return nil, merrors.InvalidArgument("quote_snapshot.route.hops are empty")
	}

	slices.SortFunc(hops, func(a, b normalizedHop) int {
		switch {
		case a.index < b.index:
			return -1
		case a.index > b.index:
			return 1
		case a.pos < b.pos:
			return -1
		case a.pos > b.pos:
			return 1
		default:
			return 0
		}
	})

	return hops, nil
}

func normalizeHopRole(
	role paymenttypes.QuoteRouteHopRole,
	position int,
	total int,
	_ model.PaymentIntent,
) paymenttypes.QuoteRouteHopRole {
	switch role {
	case paymenttypes.QuoteRouteHopRoleSource,
		paymenttypes.QuoteRouteHopRoleTransit,
		paymenttypes.QuoteRouteHopRoleDestination:
		return role
	}

	if total <= 1 {
		return paymenttypes.QuoteRouteHopRoleDestination
	}
	if position == 0 {
		return paymenttypes.QuoteRouteHopRoleSource
	}
	if position == total-1 {
		return paymenttypes.QuoteRouteHopRoleDestination
	}
	return paymenttypes.QuoteRouteHopRoleTransit
}

func normalizeRail(raw string) model.Rail {
	return model.ParseRail(raw)
}
