package orchestrator

import (
	"context"
	"strings"
	"time"

	chainclient "github.com/tech/sendico/gateway/chain/client"
	ledgerclient "github.com/tech/sendico/ledger/client"
	"github.com/tech/sendico/payments/orchestrator/internal/service/orchestrationv2/psvc"
	"github.com/tech/sendico/payments/storage/model"
	clockpkg "github.com/tech/sendico/pkg/clock"
	"github.com/tech/sendico/pkg/discovery"
	mb "github.com/tech/sendico/pkg/messaging/broker"
	"github.com/tech/sendico/pkg/mlogger"
	feesv1 "github.com/tech/sendico/pkg/proto/billing/fees/v1"
)

// Option configures Service construction.
//
// Orchestration runtime is v2-only; legacy option knobs are retained as no-op
// compatibility shims for server wiring.
type Option func(*Service)

// GatewayInvokeResolver resolves gateway invoke URIs into chain gateway clients.
type GatewayInvokeResolver interface {
	Resolve(ctx context.Context, invokeURI string) (chainclient.Client, error)
}

// GatewayRegistry exposes gateway descriptors.
type GatewayRegistry interface {
	List(ctx context.Context) ([]*model.GatewayInstanceDescriptor, error)
}

// CardGatewayRoute maps a gateway to its funding and fee destinations.
type CardGatewayRoute struct {
	FundingAddress string
	FeeAddress     string
	FeeWalletRef   string
}

// WithFeeEngine is retained for backward-compatible wiring and is currently a no-op.
func WithFeeEngine(_ feesv1.FeeEngineClient, _ time.Duration) Option {
	return func(*Service) {}
}

// WithLedgerClient configures internal ledger execution for ledger-bound steps.
func WithLedgerClient(client ledgerclient.Client) Option {
	return func(s *Service) {
		if s == nil {
			return
		}
		s.ledgerClient = client
	}
}

// WithPaymentGatewayBroker wires broker subscription for payment gateway execution events.
func WithPaymentGatewayBroker(broker mb.Broker) Option {
	return func(s *Service) {
		if s == nil || broker == nil {
			return
		}
		s.paymentGatewayBroker = broker
	}
}

// WithClock is retained for backward-compatible wiring and is currently a no-op.
func WithClock(_ clockpkg.Clock) Option {
	return func(*Service) {}
}

// WithMaxFXQuoteTTLMillis is retained for backward-compatible wiring and is currently a no-op.
func WithMaxFXQuoteTTLMillis(_ int64) Option {
	return func(*Service) {}
}

// WithGatewayInvokeResolver configures invoke-URI-to-chain-client resolution.
func WithGatewayInvokeResolver(resolver GatewayInvokeResolver) Option {
	return func(s *Service) {
		if s == nil {
			return
		}
		s.gatewayInvokeResolver = resolver
	}
}

// WithCardGatewayRoutes configures card gateway funding/fee route metadata.
func WithCardGatewayRoutes(routes map[string]CardGatewayRoute) Option {
	return func(s *Service) {
		if s == nil {
			return
		}
		s.cardGatewayRoutes = cloneCardGatewayRoutes(routes)
	}
}

// WithFeeLedgerAccounts is retained for backward-compatible wiring and is currently a no-op.
func WithFeeLedgerAccounts(_ map[string]string) Option {
	return func(*Service) {}
}

// WithGatewayRegistry configures runtime gateway descriptor discovery.
func WithGatewayRegistry(registry GatewayRegistry) Option {
	return func(s *Service) {
		if s == nil {
			return
		}
		s.gatewayRegistry = registry
	}
}

// WithBatchOptimizationPolicy configures policy-based batch optimizer behavior.
func WithBatchOptimizationPolicy(policy psvc.BatchOptimizationPolicy) Option {
	return func(s *Service) {
		if s == nil {
			return
		}
		s.batchOptimizationPolicy = policy
	}
}

type discoveryGatewayRegistry struct {
	registry *discovery.Registry
}

// NewDiscoveryGatewayRegistry adapts discovery registry entries to gateway descriptors.
func NewDiscoveryGatewayRegistry(_ mlogger.Logger, registry *discovery.Registry) GatewayRegistry {
	if registry == nil {
		return nil
	}
	return &discoveryGatewayRegistry{registry: registry}
}

func (r *discoveryGatewayRegistry) List(_ context.Context) ([]*model.GatewayInstanceDescriptor, error) {
	if r == nil || r.registry == nil {
		return nil, nil
	}

	entries := r.registry.List(time.Now(), true)
	items := make([]*model.GatewayInstanceDescriptor, 0, len(entries))
	for _, entry := range entries {
		rail := railFromDiscovery(entry.Rail)
		if rail == discovery.RailUnspecified {
			continue
		}
		operations := operationsFromDiscovery(entry.Operations)
		items = append(items, &model.GatewayInstanceDescriptor{
			ID:           strings.TrimSpace(entry.ID),
			InstanceID:   strings.TrimSpace(entry.InstanceID),
			Rail:         rail,
			Network:      strings.ToUpper(strings.TrimSpace(entry.Network)),
			InvokeURI:    strings.TrimSpace(entry.InvokeURI),
			Currencies:   currenciesFromDiscovery(entry.Currencies),
			Operations:   operations,
			Capabilities: model.RailCapabilitiesFromOperations(operations),
			Limits:       limitsFromDiscovery(entry.Limits),
			IsEnabled:    entry.Healthy,
		})
	}
	return items, nil
}

func railFromDiscovery(value string) model.Rail {
	switch discovery.NormalizeRail(value) {
	case discovery.RailCrypto:
		return discovery.RailCrypto
	case discovery.RailProviderSettlement:
		return discovery.RailProviderSettlement
	case discovery.RailLedger:
		return discovery.RailLedger
	case discovery.RailCardPayout:
		return discovery.RailCardPayout
	case discovery.RailFiatOnRamp:
		return discovery.RailFiatOnRamp
	default:
		return discovery.RailUnspecified
	}
}

func operationsFromDiscovery(values []string) []model.RailOperation {
	return model.NormalizeRailOperationStrings(discovery.NormalizeRailOperations(values))
}

func currenciesFromDiscovery(values []string) []string {
	if len(values) == 0 {
		return nil
	}
	result := make([]string, 0, len(values))
	seen := map[string]bool{}
	for _, value := range values {
		currency := strings.ToUpper(strings.TrimSpace(value))
		if currency == "" || seen[currency] {
			continue
		}
		seen[currency] = true
		result = append(result, currency)
	}
	if len(result) == 0 {
		return nil
	}
	return result
}

func limitsFromDiscovery(src *discovery.Limits) model.Limits {
	limits := model.Limits{}
	if src == nil {
		return limits
	}

	limits.MinAmount = strings.TrimSpace(src.MinAmount)
	limits.MaxAmount = strings.TrimSpace(src.MaxAmount)

	if len(src.VolumeLimit) > 0 {
		limits.VolumeLimit = map[string]string{}
		for bucket, value := range src.VolumeLimit {
			key := strings.TrimSpace(bucket)
			amount := strings.TrimSpace(value)
			if key == "" || amount == "" {
				continue
			}
			limits.VolumeLimit[key] = amount
		}
	}

	if len(src.VelocityLimit) > 0 {
		limits.VelocityLimit = map[string]int{}
		for bucket, value := range src.VelocityLimit {
			key := strings.TrimSpace(bucket)
			if key == "" || value <= 0 {
				continue
			}
			limits.VelocityLimit[key] = value
		}
	}

	return limits
}

func cloneCardGatewayRoutes(src map[string]CardGatewayRoute) map[string]CardGatewayRoute {
	if len(src) == 0 {
		return nil
	}
	out := make(map[string]CardGatewayRoute, len(src))
	for key, route := range src {
		normalizedKey := strings.TrimSpace(strings.ToLower(key))
		if normalizedKey == "" {
			continue
		}
		out[normalizedKey] = CardGatewayRoute{
			FundingAddress: strings.TrimSpace(route.FundingAddress),
			FeeAddress:     strings.TrimSpace(route.FeeAddress),
			FeeWalletRef:   strings.TrimSpace(route.FeeWalletRef),
		}
	}
	if len(out) == 0 {
		return nil
	}
	return out
}
