package orchestrator

import (
	"context"
	"sort"
	"strings"
	"time"

	"github.com/tech/sendico/payments/orchestrator/storage/model"
	"github.com/tech/sendico/pkg/discovery"
	"github.com/tech/sendico/pkg/mlogger"
)

type discoveryGatewayRegistry struct {
	logger   mlogger.Logger
	registry *discovery.Registry
}

func NewDiscoveryGatewayRegistry(logger mlogger.Logger, registry *discovery.Registry) GatewayRegistry {
	if registry == nil {
		return nil
	}
	if logger != nil {
		logger = logger.Named("discovery_gateway_registry")
	}
	return &discoveryGatewayRegistry{
		logger:   logger,
		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 {
		if entry.Rail == "" {
			continue
		}
		rail := railFromDiscovery(entry.Rail)
		if rail == model.RailUnspecified {
			continue
		}
		items = append(items, &model.GatewayInstanceDescriptor{
			ID:           entry.ID,
			InstanceID:   entry.InstanceID,
			Rail:         rail,
			Network:      entry.Network,
			InvokeURI:    strings.TrimSpace(entry.InvokeURI),
			Currencies:   normalizeCurrencies(entry.Currencies),
			Capabilities: capabilitiesFromOps(entry.Operations),
			Limits:       limitsFromDiscovery(entry.Limits),
			Version:      entry.Version,
			IsEnabled:    entry.Healthy,
		})
	}
	sort.Slice(items, func(i, j int) bool {
		return items[i].ID < items[j].ID
	})
	return items, nil
}

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

func capabilitiesFromOps(ops []string) model.RailCapabilities {
	var cap model.RailCapabilities
	for _, op := range ops {
		switch strings.ToLower(strings.TrimSpace(op)) {
		case "payin.crypto", "payin.card", "payin.fiat":
			cap.CanPayIn = true
		case "payout.crypto", "payout.card", "payout.fiat":
			cap.CanPayOut = true
		case "balance.read":
			cap.CanReadBalance = true
		case "fee.send":
			cap.CanSendFee = true
		case "observe.confirm", "observe.confirmation":
			cap.RequiresObserveConfirm = true
		case "block", "funds.block", "balance.block", "ledger.block":
			cap.CanBlock = true
		case "release", "funds.release", "balance.release", "ledger.release":
			cap.CanRelease = true
		}
	}
	return cap
}

func limitsFromDiscovery(src *discovery.Limits) model.Limits {
	if src == nil {
		return model.Limits{}
	}
	limits := model.Limits{
		MinAmount:     strings.TrimSpace(src.MinAmount),
		MaxAmount:     strings.TrimSpace(src.MaxAmount),
		VolumeLimit:   map[string]string{},
		VelocityLimit: map[string]int{},
	}
	for key, value := range src.VolumeLimit {
		k := strings.TrimSpace(key)
		v := strings.TrimSpace(value)
		if k == "" || v == "" {
			continue
		}
		limits.VolumeLimit[k] = v
	}
	for key, value := range src.VelocityLimit {
		k := strings.TrimSpace(key)
		if k == "" {
			continue
		}
		limits.VelocityLimit[k] = value
	}
	if len(limits.VolumeLimit) == 0 {
		limits.VolumeLimit = nil
	}
	if len(limits.VelocityLimit) == 0 {
		limits.VelocityLimit = nil
	}
	return limits
}
