package openai

import (
	"encoding/json"
	"fmt"
	"iop/packages/go/config"
)

func validateThinkControl(req *chatCompletionRequest) error {
	if req.ThinkingTokenBudget != nil && *req.ThinkingTokenBudget < 0 {
		return fmt.Errorf("thinking_token_budget must be non-negative")
	}
	if req.ReasoningEffort != nil {
		eff := *req.ReasoningEffort
		if eff == "" {
			return fmt.Errorf("reasoning_effort cannot be empty when present")
		}
		if eff != "none" && eff != "low" && eff != "medium" && eff != "high" && eff != "xhigh" && eff != "max" {
			return fmt.Errorf("reasoning_effort must be one of none, low, medium, high, xhigh, or max")
		}
	}
	if req.Think != nil && !*req.Think {
		if req.ReasoningEffort != nil && *req.ReasoningEffort != "none" {
			return fmt.Errorf("think=false conflicts with reasoning_effort=%s", *req.ReasoningEffort)
		}
	}
	if req.ThinkingTokenBudget != nil {
		if req.Think != nil && !*req.Think {
			return fmt.Errorf("thinking_token_budget cannot be set when think=false")
		}
		if req.ReasoningEffort != nil && *req.ReasoningEffort == "none" {
			return fmt.Errorf("thinking_token_budget cannot be set when reasoning_effort=none")
		}
	}
	return nil
}

func applyModelCatalogGenerationPolicyToChat(req *chatCompletionRequest, entry config.ModelCatalogEntry, strictOutput bool, providerNativeThinking bool) {
	if req == nil {
		return
	}
	applyOutputTokenPolicy(&req.MaxTokens, req.MaxCompletionTokens, entry.DefaultMaxTokens, entry.MinMaxTokens)
	if entry.DefaultThinkingTokenBudget > 0 && !providerNativeThinking && chatRequestAllowsDefaultThinkingBudget(*req) {
		if req.ThinkingTokenBudget == nil {
			req.ThinkingTokenBudget = intPtr(entry.DefaultThinkingTokenBudget)
		}
		if strictOutput && req.Think == nil {
			req.Think = boolPtr(true)
		}
	}
}

func applyModelCatalogGenerationPolicyToResponses(req *responsesRequest, entry config.ModelCatalogEntry) {
	if req == nil {
		return
	}
	applyOutputTokenPolicy(&req.MaxOutputTokens, nil, entry.DefaultMaxTokens, entry.MinMaxTokens)
}

func applyOutputTokenPolicy(primary **int, fallback *int, defaultTokens, minTokens int) {
	if primary == nil {
		return
	}
	current := 0
	if *primary != nil {
		current = **primary
	} else if fallback != nil {
		current = *fallback
	}

	next := current
	if next == 0 && defaultTokens > 0 {
		next = defaultTokens
	}
	if minTokens > 0 && (next == 0 || next < minTokens) {
		next = minTokens
	}
	if next > 0 && next != current {
		*primary = intPtr(next)
	}
}

func chatRequestAllowsDefaultThinkingBudget(req chatCompletionRequest) bool {
	if req.Think != nil && !*req.Think {
		return false
	}
	if req.ReasoningEffort != nil && *req.ReasoningEffort == "none" {
		return false
	}
	return true
}

func chatRequestHasProviderNativeThinking(rawBody []byte) bool {
	var raw map[string]json.RawMessage
	if err := json.Unmarshal(rawBody, &raw); err != nil {
		return false
	}
	ctkRaw, ok := raw["chat_template_kwargs"]
	if !ok {
		return false
	}
	var ctk map[string]json.RawMessage
	if err := json.Unmarshal(ctkRaw, &ctk); err != nil {
		return false
	}
	_, hasEnableThinking := ctk["enable_thinking"]
	_, hasBudget := ctk["thinking_token_budget"]
	return hasEnableThinking || hasBudget
}

func intPtr(v int) *int {
	return &v
}

func boolPtr(v bool) *bool {
	return &v
}

func chatRunMetadata(runMeta map[string]string, req chatCompletionRequest, outputPolicy strictOutputPolicy) map[string]string {
	if runMeta == nil {
		runMeta = make(map[string]string)
	}
	runMeta["openai_model"] = req.Model
	runMeta["openai_stream"] = fmt.Sprintf("%t", req.Stream)
	runMeta["strict_output"] = fmt.Sprintf("%t", outputPolicy.Strict)
	return runMeta
}

func (s *Server) resolveStrictOutput() bool {
	return s.cfg.StrictOutput
}

func (s *Server) resolveStrictStreamBuffer() bool {
	return s.cfg.StrictStreamBuffer
}

func (s *Server) resolveOutputPolicy(prompt string) strictOutputPolicy {
	policy := strictOutputPolicy{
		Strict:       s.resolveStrictOutput(),
		StreamBuffer: s.resolveStrictStreamBuffer(),
	}
	if !policy.Strict {
		return policy
	}
	policy.XMLCompletionTool, policy.XMLResultTag = inferXMLCompletionContract(prompt)
	policy.ContractInstruction = policy.XMLCompletionTool != ""
	return policy
}
