package clients

import (
	"fmt"
	"math"
	"strconv"
	"strings"

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)

const OpenAIReasoningEffortValidationMessage = "reasoning_effort must be one of: none, minimal, low, medium, high, xhigh, max"

var (
	chatReasoningEffortOrder = []string{"none", "minimal", "low", "medium", "high", "xhigh", "max"}
	openAIReasoningEfforts   = map[string]struct{}{
		"none":    {},
		"minimal": {},
		"low":     {},
		"medium":  {},
		"high":    {},
		"xhigh":   {},
		"max":     {},
	}
	volcesChatReasoningEfforts = map[string]struct{}{
		"minimal": {},
		"low":     {},
		"medium":  {},
		"high":    {},
	}
	zhipuReasoningEfforts = map[string]struct{}{
		"none":    {},
		"minimal": {},
		"low":     {},
		"medium":  {},
		"high":    {},
		"xhigh":   {},
	}
)

func ValidateOpenAIReasoningEffort(value any) error {
	effort := normalizedReasoningString(value)
	if effort == "" {
		return nil
	}
	if isOpenAIReasoningEffort(effort) {
		return nil
	}
	return &ClientError{Code: "invalid_parameter", Message: OpenAIReasoningEffortValidationMessage, Retryable: false}
}

func applyOpenAIChatReasoningParams(body map[string]any, candidate store.RuntimeModelCandidate) {
	_ = applyOpenAIChatReasoningParamsWithSource(body, candidate, nil)
}

func applyOpenAIChatReasoningParamsWithSource(body map[string]any, candidate store.RuntimeModelCandidate, original map[string]any) error {
	effort := normalizedReasoningString(body["reasoning_effort"])
	_, explicitEffort := original["reasoning_effort"]
	_, explicitTemperature := original["temperature"]
	model := chatReasoningModelName(body, candidate)
	if isAliyunBailianOpenAI(candidate) && isAliyunQwen38MaxPreview(model) {
		if explicitEffort && qwen38MaxPreviewReasoningEffort(effort) != effort {
			return explicitParameterAdaptationError("reasoning_effort", "the selected upstream only supports low, medium, or xhigh without changing the requested reasoning semantics")
		}
		if temperature, ok := finiteFloatFromAny(body["temperature"]); explicitTemperature && ok && temperature < 0.6 {
			return explicitParameterAdaptationError("temperature", "the selected upstream requires temperature >= 0.6")
		}
		applyAliyunQwen38Reasoning(body, effort)
		if temperature, ok := finiteFloatFromAny(body["temperature"]); ok && temperature < 0.6 {
			body["temperature"] = 0.6
		}
		return nil
	}
	if effort == "" || !isOpenAIReasoningEffort(effort) {
		return nil
	}
	resolved, state := resolveCandidateReasoningEffort(effort, candidate)
	if state == reasoningCapabilityUnsupported {
		if explicitEffort {
			return explicitParameterAdaptationError("reasoning_effort", "the selected upstream does not support reasoning_effort")
		}
		delete(body, "reasoning_effort")
		return nil
	}
	if resolved != "" {
		if explicitEffort && resolved != effort {
			return explicitParameterAdaptationError("reasoning_effort", "the selected upstream does not support the requested reasoning effort exactly")
		}
		effort = resolved
	}
	if explicitEffort {
		if err := validateExplicitProviderReasoningEffort(effort, candidate, model); err != nil {
			return err
		}
	}
	body["reasoning_effort"] = effort

	switch {
	case isAliyunBailianOpenAI(candidate):
		applyAliyunReasoning(body, candidate, effort)
	case isDeepSeekOpenAI(candidate):
		applyHighMaxThinkingReasoning(body, effort)
	case isZhipuOpenAI(candidate):
		applyZhipuReasoning(body, candidate, effort)
	case isVolcesOpenAI(candidate):
		applyVolcesReasoning(body, candidate, effort)
	}
	return nil
}

func validateExplicitProviderReasoningEffort(effort string, candidate store.RuntimeModelCandidate, model string) error {
	if effort == "none" {
		return nil
	}
	unsupported := func() error {
		return explicitParameterAdaptationError("reasoning_effort", "the selected upstream cannot preserve the requested reasoning effort exactly")
	}
	switch {
	case isAliyunBailianOpenAI(candidate):
		if !isAliyunHighMaxReasoningModel(model) || highMaxReasoningEffort(effort) != effort {
			return unsupported()
		}
	case isDeepSeekOpenAI(candidate):
		if highMaxReasoningEffort(effort) != effort {
			return unsupported()
		}
	case isZhipuOpenAI(candidate):
		if !isZhipuReasoningEffortModel(model) || zhipuReasoningEffort(effort) != effort {
			return unsupported()
		}
	case isVolcesOpenAI(candidate):
		if !isVolcesReasoningEffortModel(model) || volcesChatReasoningEffort(effort) != effort {
			return unsupported()
		}
	}
	return nil
}

func applyOpenAIResponsesReasoningParams(body map[string]any, candidate store.RuntimeModelCandidate) {
	_ = applyOpenAIResponsesReasoningParamsWithSource(body, candidate, nil)
}

func applyOpenAIResponsesReasoningParamsWithSource(body map[string]any, candidate store.RuntimeModelCandidate, original map[string]any) error {
	reasoning, _ := body["reasoning"].(map[string]any)
	if reasoning != nil {
		reasoning = cloneBody(reasoning)
	}
	effort := ""
	if reasoning != nil {
		effort = normalizedReasoningString(reasoning["effort"])
	}
	originalReasoning, _ := original["reasoning"].(map[string]any)
	_, explicitEffort := originalReasoning["effort"]
	_, explicitTemperature := original["temperature"]
	model := chatReasoningModelName(body, candidate)
	qwen38 := isAliyunBailianOpenAI(candidate) && isAliyunQwen38MaxPreview(model)
	if qwen38 {
		if effort == "" {
			if enabled, ok := body["enable_thinking"].(bool); ok && !enabled {
				effort = "low"
			}
		} else {
			mapped := qwen38MaxPreviewReasoningEffort(effort)
			if explicitEffort && mapped != effort {
				return explicitParameterAdaptationError("reasoning.effort", "the selected upstream only supports low, medium, or xhigh without changing the requested reasoning semantics")
			}
			effort = mapped
		}
	} else if effort != "" && isOpenAIReasoningEffort(effort) {
		resolved, state := resolveCandidateReasoningEffort(effort, candidate)
		if state == reasoningCapabilityUnsupported {
			if explicitEffort {
				return explicitParameterAdaptationError("reasoning.effort", "the selected upstream does not support reasoning.effort")
			}
			effort = ""
		} else if resolved != "" {
			if explicitEffort && resolved != effort {
				return explicitParameterAdaptationError("reasoning.effort", "the selected upstream does not support the requested reasoning effort exactly")
			}
			effort = resolved
		}
	}

	if effort != "" {
		if reasoning == nil {
			reasoning = map[string]any{}
		}
		reasoning["effort"] = effort
	} else if reasoning != nil {
		delete(reasoning, "effort")
	}
	if len(reasoning) > 0 {
		body["reasoning"] = reasoning
	} else {
		delete(body, "reasoning")
	}
	delete(body, "reasoning_effort")
	delete(body, "enable_thinking")
	if qwen38 {
		if temperature, ok := finiteFloatFromAny(body["temperature"]); ok && temperature < 0.6 {
			if explicitTemperature {
				return explicitParameterAdaptationError("temperature", "the selected upstream requires temperature >= 0.6")
			}
			body["temperature"] = 0.6
		}
	}
	return nil
}

func explicitParameterAdaptationError(param string, message string) error {
	return &ClientError{Code: "invalid_parameter", Message: message, Param: param, StatusCode: 400, Retryable: false}
}

func applyAliyunQwen38Reasoning(body map[string]any, effort string) {
	defer delete(body, "thinking_budget_tokens")
	requestedDisable := false
	if enabled, ok := body["enable_thinking"].(bool); ok && !enabled {
		requestedDisable = true
	}
	body["enable_thinking"] = true
	budget, hasBudget := nonNegativeIntFromAny(body["thinking_budget"])
	if !hasBudget {
		budget, hasBudget = nonNegativeIntFromAny(body["thinking_budget_tokens"])
	}
	if hasBudget {
		if budget > 262144 {
			budget = 262144
		}
		body["thinking_budget"] = budget
		delete(body, "reasoning_effort")
		return
	}
	delete(body, "thinking_budget")
	if effort == "" {
		if requestedDisable {
			body["reasoning_effort"] = "low"
		}
		return
	}
	body["reasoning_effort"] = qwen38MaxPreviewReasoningEffort(effort)
}

func qwen38MaxPreviewReasoningEffort(effort string) string {
	switch effort {
	case "none", "minimal", "low":
		return "low"
	case "medium":
		return "medium"
	default:
		return "xhigh"
	}
}

type reasoningCapabilityState int

const (
	reasoningCapabilityUnknown reasoningCapabilityState = iota
	reasoningCapabilityUnsupported
	reasoningCapabilitySupported
)

func resolveCandidateReasoningEffort(effort string, candidate store.RuntimeModelCandidate) (string, reasoningCapabilityState) {
	supported := map[string]struct{}{}
	declaredLevels := false
	explicitlyUnsupported := false
	explicitlySupported := false
	for _, key := range []string{"text_generate", "tools_call", "image_analysis", "video_understanding", "audio_understanding", "omni"} {
		capability, ok := candidate.Capabilities[key].(map[string]any)
		if !ok {
			continue
		}
		if value, ok := capability["supportThinking"].(bool); ok && !value {
			explicitlyUnsupported = true
			continue
		}
		if value, ok := capability["supportThinking"].(bool); ok && value {
			explicitlySupported = true
		}
		rawLevels, ok := capability["thinkingEffortLevels"].([]any)
		if !ok {
			if stringsValue, stringsOK := capability["thinkingEffortLevels"].([]string); stringsOK {
				rawLevels = make([]any, len(stringsValue))
				for index, value := range stringsValue {
					rawLevels[index] = value
				}
				ok = true
			}
		}
		if !ok {
			continue
		}
		declaredLevels = true
		for _, raw := range rawLevels {
			level := normalizedReasoningString(raw)
			if isOpenAIReasoningEffort(level) {
				supported[level] = struct{}{}
			}
		}
	}
	if len(supported) == 0 {
		if declaredLevels || (explicitlyUnsupported && !explicitlySupported) {
			return "", reasoningCapabilityUnsupported
		}
		return effort, reasoningCapabilityUnknown
	}
	requestedIndex := reasoningEffortIndex(effort)
	best := ""
	bestDistance := len(chatReasoningEffortOrder) + 1
	for index, candidateEffort := range chatReasoningEffortOrder {
		if _, ok := supported[candidateEffort]; !ok {
			continue
		}
		distance := index - requestedIndex
		if distance < 0 {
			distance = -distance
		}
		if distance < bestDistance {
			best = candidateEffort
			bestDistance = distance
		}
	}
	return best, reasoningCapabilitySupported
}

func reasoningEffortIndex(effort string) int {
	for index, value := range chatReasoningEffortOrder {
		if value == effort {
			return index
		}
	}
	return 0
}

func applyAliyunReasoning(body map[string]any, candidate store.RuntimeModelCandidate, effort string) {
	defer delete(body, "thinking_budget_tokens")
	budget, hasBudget := positiveIntFromAny(body["thinking_budget"])
	if !hasBudget {
		budget, hasBudget = positiveIntFromAny(body["thinking_budget_tokens"])
	}
	delete(body, "thinking_budget")

	if effort == "none" {
		body["enable_thinking"] = false
		delete(body, "reasoning_effort")
		return
	}

	body["enable_thinking"] = true
	model := chatReasoningModelName(body, candidate)
	if isAliyunQwen38MaxPreview(model) {
		if budget, ok := positiveIntFromAny(body["thinking_budget_tokens"]); ok {
			body["thinking_budget"] = budget
			delete(body, "reasoning_effort")
			return
		}
		body["reasoning_effort"] = qwen38MaxPreviewReasoningEffort(effort)
		return
	}
	if isAliyunHighMaxReasoningModel(model) {
		body["reasoning_effort"] = highMaxReasoningEffort(effort)
		return
	}

	delete(body, "reasoning_effort")
	if isAliyunThinkingBudgetModel(model) {
		if hasBudget {
			body["thinking_budget"] = budget
		}
	}
}

func applyHighMaxThinkingReasoning(body map[string]any, effort string) {
	if effort == "none" {
		body["thinking"] = map[string]any{"type": "disabled"}
		delete(body, "reasoning_effort")
		return
	}
	body["thinking"] = map[string]any{"type": "enabled"}
	body["reasoning_effort"] = highMaxReasoningEffort(effort)
}

func applyZhipuReasoning(body map[string]any, candidate store.RuntimeModelCandidate, effort string) {
	if effort == "none" {
		body["thinking"] = map[string]any{"type": "disabled"}
		delete(body, "reasoning_effort")
		return
	}
	body["thinking"] = map[string]any{"type": "enabled"}
	if !isZhipuReasoningEffortModel(chatReasoningModelName(body, candidate)) {
		delete(body, "reasoning_effort")
		return
	}
	if mapped := zhipuReasoningEffort(effort); mapped != "" {
		body["reasoning_effort"] = mapped
		return
	}
	delete(body, "reasoning_effort")
}

func applyVolcesReasoning(body map[string]any, _ store.RuntimeModelCandidate, effort string) {
	if effort == "none" {
		body["thinking"] = map[string]any{"type": "disabled"}
		delete(body, "reasoning_effort")
		return
	}

	body["thinking"] = map[string]any{"type": "enabled"}
	if mapped := volcesChatReasoningEffort(effort); mapped != "" {
		body["reasoning_effort"] = mapped
		return
	}
	delete(body, "reasoning_effort")
}

func normalizedReasoningString(value any) string {
	if value == nil {
		return ""
	}
	text, ok := value.(string)
	if !ok {
		text = fmt.Sprint(value)
	}
	return strings.ToLower(strings.TrimSpace(text))
}

func isOpenAIReasoningEffort(effort string) bool {
	_, ok := openAIReasoningEfforts[effort]
	return ok
}

func chatReasoningModelName(body map[string]any, candidate store.RuntimeModelCandidate) string {
	for _, value := range []any{
		body["model"],
		candidate.ProviderModelName,
		candidate.ModelName,
		candidate.ModelAlias,
	} {
		if text := normalizedReasoningString(value); text != "" {
			return text
		}
	}
	return ""
}

func providerCode(candidate store.RuntimeModelCandidate) string {
	return normalizedReasoningString(candidate.Provider)
}

func baseURLCode(candidate store.RuntimeModelCandidate) string {
	return normalizedReasoningString(candidate.BaseURL)
}

func isAliyunBailianOpenAI(candidate store.RuntimeModelCandidate) bool {
	provider := providerCode(candidate)
	baseURL := baseURLCode(candidate)
	return provider == "aliyun-bailian-openai" || strings.Contains(baseURL, "dashscope.")
}

func isVolcesOpenAI(candidate store.RuntimeModelCandidate) bool {
	provider := providerCode(candidate)
	baseURL := baseURLCode(candidate)
	return provider == "volces-openai" || strings.Contains(baseURL, "volces.com") || strings.Contains(baseURL, "byteplus.com")
}

func isDeepSeekOpenAI(candidate store.RuntimeModelCandidate) bool {
	provider := providerCode(candidate)
	baseURL := baseURLCode(candidate)
	return provider == "deepseek-openai" || strings.Contains(baseURL, "api.deepseek.com")
}

func isZhipuOpenAI(candidate store.RuntimeModelCandidate) bool {
	provider := providerCode(candidate)
	baseURL := baseURLCode(candidate)
	return provider == "zhipu-openai" || strings.Contains(baseURL, "bigmodel.cn") || strings.Contains(baseURL, "api.z.ai")
}

func isAliyunHighMaxReasoningModel(model string) bool {
	return strings.Contains(model, "deepseek-v4") || strings.HasPrefix(model, "glm-")
}

func isAliyunQwen38MaxPreview(model string) bool {
	return strings.Contains(model, "qwen3.8-max-preview")
}

func isAliyunThinkingBudgetModel(model string) bool {
	return strings.Contains(model, "qwen") || strings.Contains(model, "qwq") || strings.Contains(model, "qvq") || strings.Contains(model, "kimi")
}

func isVolcesReasoningEffortModel(model string) bool {
	return strings.HasPrefix(model, "doubao-seed-2-")
}

func isZhipuReasoningEffortModel(model string) bool {
	return model == "" || strings.HasPrefix(model, "glm-5.2") || strings.HasPrefix(model, "glm-5-2")
}

func highMaxReasoningEffort(effort string) string {
	if effort == "xhigh" || effort == "max" {
		return "max"
	}
	return "high"
}

func zhipuReasoningEffort(effort string) string {
	if effort == "max" {
		return "xhigh"
	}
	if _, ok := zhipuReasoningEfforts[effort]; ok {
		return effort
	}
	return ""
}

func volcesChatReasoningEffort(effort string) string {
	switch effort {
	case "none":
		return "minimal"
	case "xhigh", "max":
		return "high"
	default:
		if _, ok := volcesChatReasoningEfforts[effort]; ok {
			return effort
		}
		return ""
	}
}

func positiveIntFromAny(value any) (int, bool) {
	if value == nil {
		return 0, false
	}
	var number float64
	switch typed := value.(type) {
	case int:
		number = float64(typed)
	case int64:
		number = float64(typed)
	case float64:
		number = typed
	case float32:
		number = float64(typed)
	case jsonNumber:
		parsed, err := typed.Float64()
		if err != nil {
			return 0, false
		}
		number = parsed
	case string:
		parsed := normalizedReasoningString(typed)
		if parsed == "" {
			return 0, false
		}
		value, err := strconv.ParseFloat(parsed, 64)
		if err != nil {
			return 0, false
		}
		number = value
	default:
		return 0, false
	}
	if math.IsNaN(number) || math.IsInf(number, 0) || number <= 0 {
		return 0, false
	}
	return int(math.Floor(number)), true
}

func nonNegativeIntFromAny(value any) (int, bool) {
	number, ok := finiteFloatFromAny(value)
	if !ok || number < 0 {
		return 0, false
	}
	return int(math.Floor(number)), true
}

func finiteFloatFromAny(value any) (float64, bool) {
	if value == nil {
		return 0, false
	}
	var number float64
	switch typed := value.(type) {
	case int:
		number = float64(typed)
	case int64:
		number = float64(typed)
	case float64:
		number = typed
	case float32:
		number = float64(typed)
	case jsonNumber:
		parsed, err := typed.Float64()
		if err != nil {
			return 0, false
		}
		number = parsed
	case string:
		parsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)
		if err != nil {
			return 0, false
		}
		number = parsed
	default:
		return 0, false
	}
	if math.IsNaN(number) || math.IsInf(number, 0) {
		return 0, false
	}
	return number, true
}

type jsonNumber interface {
	Float64() (float64, error)
}
