package capabilities

import (
	"regexp"
	"strings"

	"chat-switchboard/models"
)

// ── Known Model Defaults ────────────────────
//
// REMOVED in 0.9.1: The static known model table was removed because the same
// model ID can have different capabilities depending on the provider hosting it
// (e.g. DeepSeek on Venice has no tool_calling, same model on OpenRouter does).
//
// Capability resolution chain:
//   1. Catalog (provider API sync) — authoritative per-provider
//   2. Heuristic inference (regex on model ID) — best effort
//   3. Admin override — manual correction (shipped v0.22.0)
//
// The catalog is populated when providers are added (auto-fetch) or refreshed.
// Heuristics cover broad model families (llama→tools, gemini→vision, etc.)
// until catalog data is available.

// LookupKnownModel always returns false — kept for interface compatibility.
// Callers fall through to heuristic inference.
func LookupKnownModel(modelID string) (models.ModelCapabilities, bool) {
	return models.ModelCapabilities{}, false
}

// ── Heuristic Capability Detection ──────────

var (
	toolPatterns = []*regexp.Regexp{
		regexp.MustCompile(`(?i)llama[_-]?[34]`),
		regexp.MustCompile(`(?i)granite[_-]?[34]`),
		regexp.MustCompile(`(?i)hermes[_-]?[23]?`),
		regexp.MustCompile(`(?i)qwen[_-]?[23]`),
		regexp.MustCompile(`(?i)mistral|mixtral`),
		regexp.MustCompile(`(?i)command[_-]?r`),
		regexp.MustCompile(`(?i)phi[_-]?[34]`),
		regexp.MustCompile(`(?i)deepseek[_-]?v[23]`),
		regexp.MustCompile(`(?i)functionary`),
		regexp.MustCompile(`(?i)^gpt[_-]?[345]`),
		regexp.MustCompile(`(?i)^openai[_-]gpt`),
		regexp.MustCompile(`(?i)^claude`),
		regexp.MustCompile(`(?i)gemma[_-]?[23]`),
		regexp.MustCompile(`(?i)glm[_-]?[45]`),
		regexp.MustCompile(`(?i)gemini`),
		regexp.MustCompile(`(?i)grok`),
		regexp.MustCompile(`(?i)kimi`),
		regexp.MustCompile(`(?i)minimax`),
	}

	visionPatterns = []*regexp.Regexp{
		regexp.MustCompile(`(?i)llava|bakllava`),
		regexp.MustCompile(`(?i)moondream`),
		regexp.MustCompile(`(?i)llama.*vision|vision.*llama`),
		regexp.MustCompile(`(?i)minicpm[_-]?v`),
		regexp.MustCompile(`(?i)^gpt[_-]?4[_-]?o|^gpt[_-]?4[_-]?turbo`),
		regexp.MustCompile(`(?i)^claude[_-]?(3|opus|sonnet)`),
		regexp.MustCompile(`(?i)gemini`),
		regexp.MustCompile(`(?i)qwen.*vl|vl.*qwen`),
		regexp.MustCompile(`(?i)phi[_-]?[34].*vision`),
		regexp.MustCompile(`(?i)internvl`),
		regexp.MustCompile(`(?i)glm[_-]?4v`),
		regexp.MustCompile(`(?i)gemma[_-]?3`),
		regexp.MustCompile(`(?i)grok[_-]?4`),
		regexp.MustCompile(`(?i)mistral.*24b`),
	}

	reasoningPatterns = []*regexp.Regexp{
		regexp.MustCompile(`(?i)deepseek[_-]?r1`),
		regexp.MustCompile(`(?i)qwq`),
		regexp.MustCompile(`(?i)^o[1234][_-]|^o[1234]$`),
		regexp.MustCompile(`(?i)thinking`),
		regexp.MustCompile(`(?i)grok`),
		regexp.MustCompile(`(?i)glm[_-]?[45]`),
	}

	codePatterns = []*regexp.Regexp{
		regexp.MustCompile(`(?i)codellama|code[_-]?llama`),
		regexp.MustCompile(`(?i)deepseek[_-]?coder`),
		regexp.MustCompile(`(?i)starcoder`),
		regexp.MustCompile(`(?i)coder|codestral`),
		regexp.MustCompile(`(?i)wizardcoder`),
		regexp.MustCompile(`(?i)codegemma`),
	}
)

func matchesAny(id string, patterns []*regexp.Regexp) bool {
	for _, p := range patterns {
		if p.MatchString(id) {
			return true
		}
	}
	return false
}

// InferCapabilities guesses model capabilities from the model ID string.
// Best-effort fallback when the provider API hasn't reported capabilities.
func InferCapabilities(modelID string) models.ModelCapabilities {
	id := normalizeModelID(modelID)
	return models.ModelCapabilities{
		Streaming:     true, // virtually everything streams
		ToolCalling:   matchesAny(id, toolPatterns),
		Vision:        matchesAny(id, visionPatterns),
		Reasoning:     matchesAny(id, reasoningPatterns),
		CodeOptimized: matchesAny(id, codePatterns),
	}
}

// ResolveIntrinsic determines the intrinsic capabilities of a model.
// Priority (highest wins):
//  1. Admin overrides (manual corrections from capability_overrides table)
//  2. Catalog (from model_catalog DB — synced from provider API)
//  3. Heuristic inference (regex patterns on model ID)
//
// The catalog is the source of truth per provider. Heuristics fill gaps
// for models that haven't been synced yet. Admin overrides correct
// provider-reported or heuristic errors. No hardcoded model table —
// the same model can have different capabilities on different providers.
func ResolveIntrinsic(modelID string, catalogCaps *models.ModelCapabilities, overrides []models.CapabilityOverride) models.ModelCapabilities {
	// Start with catalog data if available
	var base models.ModelCapabilities
	if catalogCaps != nil && catalogCaps.HasProviderData() {
		base = *catalogCaps
	}

	// Fill gaps from heuristic inference
	inferred := InferCapabilities(modelID)
	mergeGaps(&base, &inferred)

	// Apply admin overrides (highest priority — can flip any field)
	applyOverrides(&base, overrides)

	return base
}

// applyOverrides applies admin capability corrections to the resolved capabilities.
func applyOverrides(caps *models.ModelCapabilities, overrides []models.CapabilityOverride) {
	for _, o := range overrides {
		boolVal := o.Value == "true" || o.Value == "1"
		switch o.Field {
		case "streaming":
			caps.Streaming = boolVal
		case "tool_calling":
			caps.ToolCalling = boolVal
		case "vision":
			caps.Vision = boolVal
		case "thinking":
			caps.Thinking = boolVal
		case "reasoning":
			caps.Reasoning = boolVal
		case "code_optimized":
			caps.CodeOptimized = boolVal
		case "web_search":
			caps.WebSearch = boolVal
		case "max_context":
			if n := parseInt(o.Value); n > 0 {
				caps.MaxContext = n
			}
		case "max_output_tokens":
			if n := parseInt(o.Value); n > 0 {
				caps.MaxOutputTokens = n
			}
		}
	}
}

// parseInt parses a string to int, returning 0 on failure.
func parseInt(s string) int {
	n := 0
	for _, c := range s {
		if c < '0' || c > '9' {
			return 0
		}
		n = n*10 + int(c-'0')
	}
	return n
}

// ResolveMaxOutput returns the max output tokens for a model.
// Priority: explicit caps → derive from context → 4096 fallback.
func ResolveMaxOutput(modelID string, caps models.ModelCapabilities) int {
	if caps.MaxOutputTokens > 0 {
		return caps.MaxOutputTokens
	}
	if caps.MaxContext > 0 {
		derived := caps.MaxContext / 8
		if derived < 2048 {
			derived = 2048
		}
		if derived > 16384 {
			derived = 16384
		}
		return derived
	}
	return 4096
}

// mergeGaps fills zero/false fields in dst from src. Never overrides existing data.
func mergeGaps(dst, src *models.ModelCapabilities) {
	if !dst.Streaming && src.Streaming {
		dst.Streaming = true
	}
	if !dst.ToolCalling && src.ToolCalling {
		dst.ToolCalling = true
	}
	if !dst.Vision && src.Vision {
		dst.Vision = true
	}
	if !dst.Thinking && src.Thinking {
		dst.Thinking = true
	}
	if !dst.Reasoning && src.Reasoning {
		dst.Reasoning = true
	}
	if !dst.CodeOptimized && src.CodeOptimized {
		dst.CodeOptimized = true
	}
	if !dst.WebSearch && src.WebSearch {
		dst.WebSearch = true
	}
	if dst.MaxContext == 0 && src.MaxContext > 0 {
		dst.MaxContext = src.MaxContext
	}
	if dst.MaxOutputTokens == 0 && src.MaxOutputTokens > 0 {
		dst.MaxOutputTokens = src.MaxOutputTokens
	}
}

// normalizeModelID strips provider prefix and lowercases.
func normalizeModelID(modelID string) string {
	id := strings.ToLower(modelID)
	if idx := strings.Index(id, "/"); idx >= 0 {
		id = id[idx+1:]
	}
	return id
}
