// Package routing implements rules-based request routing for LLM providers.
// Policies are evaluated between "what model was requested" and "which
// provider config serves it," producing a ranked list of candidates.
// The fallback runner dispatches to candidates in order, retrying on failure.
package routing

import (
	"time"

	"chat-switchboard/models"
)

// ── Policy ─────────────────────────────────

// PolicyType identifies the routing strategy.
type PolicyType string

const (
	// PolicyProviderPrefer routes to a preferred provider, falling back to others.
	PolicyProviderPrefer PolicyType = "provider_prefer"
	// PolicyTeamRoute forces a team to use specific providers.
	PolicyTeamRoute PolicyType = "team_route"
	// PolicyCostLimit caps per-request cost.
	PolicyCostLimit PolicyType = "cost_limit"
	// PolicyModelAlias maps a model alias to a specific provider+model pair.
	PolicyModelAlias PolicyType = "model_alias"
	// PolicyCapabilityMatch filters to models with required capabilities,
	// optionally sorted by cheapest output price. v0.29.1.
	PolicyCapabilityMatch PolicyType = "capability_match"
)

// Policy is one routing rule stored in the routing_policies table.
type Policy struct {
	ID        string     `json:"id" db:"id"`
	Name      string     `json:"name" db:"name"`
	Scope     string     `json:"scope" db:"scope"`          // "global" or "team"
	TeamID    *string    `json:"team_id,omitempty" db:"team_id"`
	Priority  int        `json:"priority" db:"priority"`     // lower = evaluated first
	Type      PolicyType `json:"policy_type" db:"policy_type"`
	Config    PolicyConfig `json:"config" db:"config"`
	IsActive  bool       `json:"is_active" db:"is_active"`
	CreatedAt time.Time  `json:"created_at" db:"created_at"`
	UpdatedAt time.Time  `json:"updated_at" db:"updated_at"`
}

// PolicyConfig holds the type-specific parameters for a routing policy.
// Stored as JSONB. Each PolicyType uses a subset of these fields.
type PolicyConfig struct {
	// provider_prefer: ordered list of provider config IDs
	PreferredProviders []string `json:"preferred_providers,omitempty"`

	// team_route: required provider config IDs for this team
	AllowedProviders []string `json:"allowed_providers,omitempty"`

	// cost_limit: max cost per request in USD
	MaxCostUSD *float64 `json:"max_cost_usd,omitempty"`

	// model_alias: maps alias → provider+model
	AliasFrom string `json:"alias_from,omitempty"` // e.g. "default", "fast", "smart"
	AliasTo   string `json:"alias_to,omitempty"`   // model ID
	AliasProvider string `json:"alias_provider,omitempty"` // config ID

	// capability_match: filter candidates by model capabilities
	RequiredCaps   []string `json:"required_caps,omitempty"`    // e.g. ["tool_calling", "vision"]
	PreferCheapest bool     `json:"prefer_cheapest,omitempty"`  // sort by output_price_per_m ascending

	// Shared: health requirements
	SkipDown     bool `json:"skip_down,omitempty"`     // skip providers with status "down"
	PreferHealthy bool `json:"prefer_healthy,omitempty"` // rank healthy > degraded
	MaxRetries   int  `json:"max_retries,omitempty"`   // 0 = no retry, default = 1
}

// ── Evaluation Context ─────────────────────

// Context carries the information needed to evaluate routing policies.
type Context struct {
	UserID   string
	TeamIDs  []string // teams the user belongs to
	Model    string   // requested model ID
	ConfigID string   // explicitly requested provider config (may be empty)

	// Health status for all active providers (keyed by config ID).
	// Populated from the health accumulator at evaluation time.
	HealthStatus map[string]models.ProviderStatus

	// Pricing for candidate models (keyed by "configID:modelID").
	// Populated lazily from the pricing store.
	Pricing map[string]*models.ModelPricing

	// Capabilities for candidate models (keyed by "configID:modelID").
	// Populated lazily from the catalog store. Used by capability_match.
	Capabilities map[string]*models.ModelCapabilities
}

// ── Candidates ─────────────────────────────

// Candidate is one possible (provider, model) pair the request could be routed to.
type Candidate struct {
	ConfigID   string                `json:"config_id"`
	ProviderID string                `json:"provider_id"` // "openai", "anthropic", etc.
	Model      string                `json:"model"`
	Endpoint   string                `json:"-"`           // internal, not exposed in API
	Status     models.ProviderStatus `json:"status"`
	Reason     string                `json:"reason"`      // why this candidate was selected
}

// ── Routing Decision ───────────────────────

// Decision records which candidate was selected and why.
// Stored in the usage_log for observability.
type Decision struct {
	PolicyID      string `json:"policy_id,omitempty"`      // which policy matched
	PolicyName    string `json:"policy_name,omitempty"`    // human-readable
	PolicyType    string `json:"policy_type,omitempty"`
	Selected      string `json:"selected"`                  // config ID that served the request
	FallbackDepth int    `json:"fallback_depth"`            // 0 = first choice, 1+ = fallback
	Candidates    int    `json:"candidates"`                // total candidates evaluated
}
