package walletapiimp

import (
	"context"
	"errors"
	"strings"

	"github.com/tech/sendico/pkg/discovery"
	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/model"
	connectorv1 "github.com/tech/sendico/pkg/proto/connector/v1"
	"go.uber.org/zap"
)

func normalizeNetworkName(raw string) string {
	value := strings.TrimSpace(raw)
	if value == "" {
		return ""
	}

	if idx := strings.Index(value, "-"); idx > 0 {
		value = value[:idx]
	}

	value = strings.ToLower(value)
	value = strings.TrimPrefix(value, "chain_network_")
	return strings.TrimSpace(value)
}

func (a *WalletAPI) rememberWalletRoute(ctx context.Context, organizationRef string, walletRef string, network string, gatewayID string) {
	if a.routes == nil {
		return
	}

	walletRef = strings.TrimSpace(walletRef)
	organizationRef = strings.TrimSpace(organizationRef)
	network = normalizeNetworkName(network)
	gatewayID = strings.TrimSpace(gatewayID)

	if walletRef == "" || organizationRef == "" || (network == "" && gatewayID == "") {
		return
	}

	if err := a.routes.Upsert(ctx, &model.ChainWalletRoute{
		OrganizationRef: organizationRef,
		WalletRef:       walletRef,
		Network:         network,
		GatewayID:       gatewayID,
	}); err != nil {
		a.logger.Warn("Failed to persist wallet route",
			zap.String("organization_ref", organizationRef),
			zap.String("wallet_ref", walletRef),
			zap.String("network", network),
			zap.String("gateway_id", gatewayID),
			zap.Error(err))
	} else {
		a.logger.Debug("Persisted wallet route",
			zap.String("organization_ref", organizationRef),
			zap.String("wallet_ref", walletRef),
			zap.String("network", network),
			zap.String("gateway_id", gatewayID))
	}
}

func (a *WalletAPI) walletRoute(ctx context.Context, organizationRef string, walletRef string) (*model.ChainWalletRoute, error) {
	if a.routes == nil {
		//nolint:nilnil // Routing cache is optional and may be disabled.
		return nil, nil
	}

	walletRef = strings.TrimSpace(walletRef)
	organizationRef = strings.TrimSpace(organizationRef)
	if walletRef == "" || organizationRef == "" {
		//nolint:nilnil // Missing route keys mean no cached route.
		return nil, nil
	}

	route, err := a.routes.Get(ctx, organizationRef, walletRef)
	if err != nil {
		if errors.Is(err, merrors.ErrNoData) {
			//nolint:nilnil // Route not found in cache.
			return nil, nil
		}
		return nil, err
	}
	return route, nil
}

func findGatewayForRoute(gateways []discovery.GatewaySummary, route *model.ChainWalletRoute) *discovery.GatewaySummary {
	if route == nil {
		return nil
	}

	gatewayID := strings.TrimSpace(route.GatewayID)
	if gatewayID != "" {
		for _, gw := range gateways {
			if strings.EqualFold(strings.TrimSpace(gw.ID), gatewayID) &&
				strings.EqualFold(gw.Rail, cryptoRail) &&
				gw.Healthy &&
				strings.TrimSpace(gw.InvokeURI) != "" {
				return &gw
			}
		}
	}

	return findGatewayForNetwork(gateways, route.Network)
}

func accountNetwork(account *connectorv1.Account) string {
	if account == nil {
		return ""
	}

	if details := account.GetProviderDetails(); details != nil {
		if field, ok := details.GetFields()["network"]; ok && field != nil {
			if network := normalizeNetworkName(field.GetStringValue()); network != "" {
				return network
			}
		}
	}

	return normalizeNetworkName(account.GetAsset())
}

func dropGatewayByInvokeURI(gateways []discovery.GatewaySummary, invokeURI string) []discovery.GatewaySummary {
	invokeURI = strings.ToLower(strings.TrimSpace(invokeURI))
	if invokeURI == "" || len(gateways) == 0 {
		return gateways
	}

	result := make([]discovery.GatewaySummary, 0, len(gateways))
	for _, gw := range gateways {
		if strings.ToLower(strings.TrimSpace(gw.InvokeURI)) == invokeURI {
			continue
		}
		result = append(result, gw)
	}
	return result
}
