package providers

import (
	"encoding/json"
	"strings"
)

// Hooks defines provider-specific request/response transforms.
// Implementations read from ProviderConfig.Settings (populated from the
// provider_configs.settings JSONB column) to decorate requests and
// normalize responses. This replaces hardcoded provider switch statements.
type Hooks interface {
	// PreRequest modifies the canonical request before dispatch.
	// May inject system prompt prefixes, set ExtraBody fields,
	// and adjust request parameters based on provider settings.
	PreRequest(cfg ProviderConfig, req *CompletionRequest)

	// PostStreamEvent normalizes a streaming event after the provider
	// emits it. For example, extracting thinking output from content.
	PostStreamEvent(cfg ProviderConfig, event *StreamEvent)
}

// ── Hook Registry ───────────────────────────

var hooks = map[string]Hooks{
	"openai":     &OpenAIHooks{},
	"anthropic":  &AnthropicHooks{},
	"venice":     &VeniceHooks{},
	"openrouter": &OpenRouterHooks{},
}

// GetHooks returns the hooks for a provider type.
// Returns nil for unknown types (no transforms applied).
func GetHooks(providerType string) Hooks {
	return hooks[providerType]
}

// RegisterHooks adds or replaces hooks for a provider type.
func RegisterHooks(providerType string, h Hooks) {
	hooks[providerType] = h
}

// ── Settings Helpers ────────────────────────

func settingString(s map[string]interface{}, key string) string {
	if v, ok := s[key]; ok {
		if str, ok := v.(string); ok {
			return str
		}
	}
	return ""
}

func settingBool(s map[string]interface{}, key string) bool {
	if v, ok := s[key]; ok {
		switch b := v.(type) {
		case bool:
			return b
		case string:
			return b == "true"
		}
	}
	return false
}

func settingInt(s map[string]interface{}, key string, fallback int) int {
	if v, ok := s[key]; ok {
		switch n := v.(type) {
		case float64:
			return int(n) // JSON numbers decode as float64
		case int:
			return n
		}
	}
	return fallback
}

func settingFloat(s map[string]interface{}, key string) (float64, bool) {
	if v, ok := s[key]; ok {
		switch n := v.(type) {
		case float64:
			return n, true
		case int:
			return float64(n), true
		}
	}
	return 0, false
}

// ── Common: System Prompt Prefix ────────────

// prependSystemPrompt injects a provider-level system prompt prefix
// into the first system message, or creates one if none exists.
func prependSystemPrompt(prefix string, req *CompletionRequest) {
	if prefix == "" {
		return
	}
	for i, m := range req.Messages {
		if m.Role == "system" {
			req.Messages[i].Content = prefix + "\n\n" + m.Content
			return
		}
	}
	// No system message — insert at position 0
	req.Messages = append([]Message{{Role: "system", Content: prefix}}, req.Messages...)
}

// ── OpenAI Hooks ────────────────────────────

type OpenAIHooks struct{}

func (h *OpenAIHooks) PreRequest(cfg ProviderConfig, req *CompletionRequest) {
	s := cfg.Settings
	if s == nil {
		return
	}

	prependSystemPrompt(settingString(s, "system_prompt_prefix"), req)

	if req.ExtraBody == nil {
		req.ExtraBody = make(map[string]interface{})
	}
	if v, ok := settingFloat(s, "frequency_penalty"); ok {
		req.ExtraBody["frequency_penalty"] = v
	}
	if v, ok := settingFloat(s, "presence_penalty"); ok {
		req.ExtraBody["presence_penalty"] = v
	}
}

func (h *OpenAIHooks) PostStreamEvent(_ ProviderConfig, _ *StreamEvent) {
	// No post-processing needed for vanilla OpenAI.
}

// ── Anthropic Hooks ─────────────────────────

type AnthropicHooks struct{}

func (h *AnthropicHooks) PreRequest(cfg ProviderConfig, req *CompletionRequest) {
	s := cfg.Settings
	if s == nil {
		return
	}

	prependSystemPrompt(settingString(s, "system_prompt_prefix"), req)

	if settingBool(s, "extended_thinking") {
		budget := settingInt(s, "thinking_budget", 10000)
		if req.ExtraBody == nil {
			req.ExtraBody = make(map[string]interface{})
		}
		req.ExtraBody["thinking"] = map[string]interface{}{
			"type":          "enabled",
			"budget_tokens": budget,
		}
		// Anthropic requires temperature=1 with extended thinking, and
		// thinking is incompatible with temperature overrides.
		req.Temperature = nil
	}
}

func (h *AnthropicHooks) PostStreamEvent(_ ProviderConfig, _ *StreamEvent) {
	// Anthropic streaming already normalizes thinking → Reasoning field.
}

// ── Venice Hooks ────────────────────────────

type VeniceHooks struct{}

func (h *VeniceHooks) PreRequest(cfg ProviderConfig, req *CompletionRequest) {
	s := cfg.Settings
	if s == nil {
		return
	}

	prependSystemPrompt(settingString(s, "system_prompt_prefix"), req)

	// Build venice_parameters from settings
	veniceParams := map[string]interface{}{}
	hasParams := false

	if settingBool(s, "enable_web_search") {
		veniceParams["enable_web_search"] = "always"
		hasParams = true
	}

	// include_venice_system_prompt defaults to true.
	// Only set to false when explicitly configured.
	if v, ok := s["include_venice_system_prompt"]; ok {
		if b, ok := v.(bool); ok && !b {
			veniceParams["include_venice_system_prompt"] = false
			hasParams = true
		}
	}

	if settingBool(s, "enable_thinking") {
		// Thinking requires disabling Venice system prompt
		veniceParams["include_venice_system_prompt"] = false
		hasParams = true
	}

	if hasParams {
		if req.ExtraBody == nil {
			req.ExtraBody = make(map[string]interface{})
		}
		req.ExtraBody["venice_parameters"] = veniceParams
	}
}

func (h *VeniceHooks) PostStreamEvent(cfg ProviderConfig, event *StreamEvent) {
	// Venice thinking output arrives in the regular content stream
	// wrapped in <think>...</think> tags. The stream_loop already handles
	// <think> extraction from reasoning_content, so we extract Venice
	// thinking from Delta → Reasoning when enable_thinking is on.
	if cfg.Settings == nil || !settingBool(cfg.Settings, "enable_thinking") {
		return
	}

	// Venice sends thinking content inline in Delta. The frontend
	// expects it in the Reasoning field. We detect <think> blocks
	// and move them.
	if event.Delta != "" && strings.Contains(event.Delta, "<think>") {
		// Full block in one chunk (rare but handle it)
		event.Reasoning += event.Delta
		event.Delta = ""
	}
}

// ── OpenRouter Hooks ────────────────────────

type OpenRouterHooks struct{}

func (h *OpenRouterHooks) PreRequest(cfg ProviderConfig, req *CompletionRequest) {
	s := cfg.Settings
	if s == nil {
		return
	}

	prependSystemPrompt(settingString(s, "system_prompt_prefix"), req)

	// OpenRouter route/transforms go in provider preferences header.
	// These are sent as JSON in the X-Provider-Preferences header by
	// the OpenAI doRequest (reads from ExtraBody["provider_preferences"]).
	route := settingString(s, "route")
	requireParams := settingBool(s, "require_parameters")

	if route != "" || requireParams {
		prefs := map[string]interface{}{}
		if route != "" && route != "auto" {
			prefs["route"] = route
		}
		if requireParams {
			prefs["require_parameters"] = true
		}
		// Serialize for the HTTP header (OpenRouter reads from extra headers)
		prefsJSON, _ := json.Marshal(prefs)
		if cfg.CustomHeaders == nil {
			cfg.CustomHeaders = make(map[string]string)
		}
		cfg.CustomHeaders["X-Provider-Preferences"] = string(prefsJSON)
	}
}

func (h *OpenRouterHooks) PostStreamEvent(_ ProviderConfig, _ *StreamEvent) {
	// No post-processing needed. OpenRouter returns standard OAI format.
}

// ── Persona Override Merge ───────────────────

// MergePersonaSettings merges persona-level setting overrides onto
// provider-level settings. Persona values take priority except for
// fields marked ProviderOnly in the schema.
func MergePersonaSettings(providerType string, providerSettings, personaOverrides map[string]interface{}) map[string]interface{} {
	if len(personaOverrides) == 0 {
		return providerSettings
	}

	schema := GetProfileSchema(providerType)
	providerOnly := make(map[string]bool, len(schema.Fields))
	for _, f := range schema.Fields {
		if f.ProviderOnly {
			providerOnly[f.Key] = true
		}
	}

	merged := make(map[string]interface{}, len(providerSettings)+len(personaOverrides))
	for k, v := range providerSettings {
		merged[k] = v
	}
	for k, v := range personaOverrides {
		if !providerOnly[k] {
			merged[k] = v
		}
	}
	return merged
}
