package openai

import (
	"bytes"
	"encoding/json"
	"fmt"
	"strings"

	"iop/packages/go/streamgate"
)

func decodeChatCompletionRequest(dec *json.Decoder, req *chatCompletionRequest) error {
	var raw map[string]json.RawMessage
	if err := dec.Decode(&raw); err != nil {
		return fmt.Errorf("invalid JSON request")
	}
	for key := range raw {
		switch key {
		case "model", "messages", "stream", "metadata", "max_tokens", "max_completion_tokens", "temperature", "top_p", "top_k", "presence_penalty", "frequency_penalty", "seed", "stop", "response_format", "tools", "tool_choice", "parallel_tool_calls", "stream_options", "store", "think", "reasoning_effort", "thinking_token_budget", "include_reasoning", "extra_body":
		default:
			return fmt.Errorf("%s is not supported for /v1/chat/completions", key)
		}
	}
	normalized, err := json.Marshal(raw)
	if err != nil {
		return fmt.Errorf("invalid JSON request")
	}
	if err := json.Unmarshal(normalized, req); err != nil {
		return fmt.Errorf("invalid /v1/chat/completions request format")
	}
	if req.MaxTokens != nil && *req.MaxTokens <= 0 {
		return fmt.Errorf("max_tokens must be greater than zero")
	}
	if req.MaxCompletionTokens != nil && *req.MaxCompletionTokens <= 0 {
		return fmt.Errorf("max_completion_tokens must be greater than zero")
	}
	if req.Temperature != nil && (*req.Temperature < 0 || *req.Temperature > 2) {
		return fmt.Errorf("temperature must be between 0 and 2")
	}
	if req.TopP != nil && (*req.TopP < 0 || *req.TopP > 1) {
		return fmt.Errorf("top_p must be between 0 and 1")
	}
	if req.TopK != nil && *req.TopK <= 0 {
		return fmt.Errorf("top_k must be greater than zero")
	}
	if err := validateThinkControl(req); err != nil {
		return err
	}
	return nil
}

// chatCompletionEnvelope holds the routing-relevant fields decoded leniently
// from a /v1/chat/completions request body before strict normalization. Unknown
// fields are ignored so the provider tunnel passthrough can forward Codex/
// provider-specific payloads verbatim. Strict field validation stays on the
// normalized non-provider path.
type chatCompletionEnvelope struct {
	Model    string          `json:"model"`
	Metadata json.RawMessage `json:"metadata,omitempty"`
	Stream   bool            `json:"stream"`
}

// decodeChatCompletionEnvelope leniently extracts only the routing-relevant
// fields (model, metadata, stream) from a /v1/chat/completions request body.
// It uses json.Decoder to read only the first JSON value so caller-provided
// trailing payload (provider-specific extensions beyond the root object) does
// not invalidate the envelope; strict field validation stays on the normalized
// non-provider path.
func decodeChatCompletionEnvelope(rawBody []byte) (chatCompletionEnvelope, error) {
	var env chatCompletionEnvelope
	dec := json.NewDecoder(bytes.NewReader(rawBody))
	if err := dec.Decode(&env); err != nil {
		return chatCompletionEnvelope{}, fmt.Errorf("invalid JSON request")
	}
	return env, nil
}

// decodeChatCompletionRequestLenient decodes a /v1/chat/completions request
// body while preserving unknown top-level fields. Known-field syntax and
// validation are still enforced so malformed standard fields cannot reach the
// provider tunnel. This is the provider-pool ingress path.
func decodeChatCompletionRequestLenient(dec *json.Decoder, req *chatCompletionRequest) error {
	var raw map[string]json.RawMessage
	if err := dec.Decode(&raw); err != nil {
		return fmt.Errorf("invalid JSON request")
	}
	for key := range raw {
		switch key {
		case "model", "messages", "stream", "metadata", "max_tokens", "max_completion_tokens", "temperature", "top_p", "top_k", "presence_penalty", "frequency_penalty", "seed", "stop", "response_format", "tools", "tool_choice", "parallel_tool_calls", "stream_options", "store", "think", "reasoning_effort", "thinking_token_budget", "include_reasoning", "extra_body":
		default:
			// Unknown fields are tolerated for provider-pool passthrough.
		}
	}
	normalized, err := json.Marshal(raw)
	if err != nil {
		return fmt.Errorf("invalid JSON request")
	}
	if err := json.Unmarshal(normalized, req); err != nil {
		return fmt.Errorf("invalid /v1/chat/completions request format")
	}
	// Reuse the same validation rules on known fields for consistency.
	if req.MaxTokens != nil && *req.MaxTokens <= 0 {
		return fmt.Errorf("max_tokens must be greater than zero")
	}
	if req.MaxCompletionTokens != nil && *req.MaxCompletionTokens <= 0 {
		return fmt.Errorf("max_completion_tokens must be greater than zero")
	}
	if req.Temperature != nil && (*req.Temperature < 0 || *req.Temperature > 2) {
		return fmt.Errorf("temperature must be between 0 and 2")
	}
	if req.TopP != nil && (*req.TopP < 0 || *req.TopP > 1) {
		return fmt.Errorf("top_p must be between 0 and 1")
	}
	if req.TopK != nil && *req.TopK <= 0 {
		return fmt.Errorf("top_k must be greater than zero")
	}
	if err := validateThinkControl(req); err != nil {
		return err
	}
	return nil
}

// decodeOpenAIChatRepeatHistory builds the Chat-specific, raw-free repeat
// history view. Only plain role text, the documented reasoning aliases, and
// completed tool call/result pairs participate. Signed, encrypted, malformed,
// and unknown fields remain in the canonical ingress snapshot but are ignored
// by this semantic decoder.
func decodeOpenAIChatRepeatHistory(rawBody []byte) (openAIRepeatHistorySnapshot, error) {
	var root struct {
		Messages []json.RawMessage `json:"messages"`
	}
	if err := json.Unmarshal(rawBody, &root); err != nil {
		return openAIRepeatHistorySnapshot{}, fmt.Errorf("decode Chat repeat history: %w", err)
	}
	builder := newOpenAIRepeatHistoryBuilder()
	pendingActions := make(map[string]streamgate.FixedFingerprint)
	for _, rawMessage := range root.Messages {
		var message map[string]json.RawMessage
		if err := json.Unmarshal(rawMessage, &message); err != nil {
			continue
		}
		var role string
		if err := json.Unmarshal(message["role"], &role); err != nil {
			continue
		}
		role = strings.ToLower(strings.TrimSpace(role))
		for _, text := range openAIRepeatPlainTextParts(message["content"]) {
			builder.recordText(role, openAIRepeatChannelContent, text)
		}
		for _, alias := range []string{"reasoning_content", "reasoning", "reasoning_text"} {
			var reasoning string
			if err := json.Unmarshal(message[alias], &reasoning); err == nil {
				builder.recordText(role, openAIRepeatChannelReasoning, reasoning)
			}
		}
		if role == "assistant" {
			var calls []json.RawMessage
			if json.Unmarshal(message["tool_calls"], &calls) == nil {
				for _, rawCall := range calls {
					var call struct {
						ID       string `json:"id"`
						Type     string `json:"type"`
						Function struct {
							Name      string          `json:"name"`
							Arguments json.RawMessage `json:"arguments"`
						} `json:"function"`
					}
					if json.Unmarshal(rawCall, &call) != nil {
						continue
					}
					if fingerprint, ok := openAIRepeatActionFingerprint(
						call.Function.Name,
						openAIRepeatArgumentsValue(call.Function.Arguments),
					); ok && strings.TrimSpace(call.ID) != "" {
						pendingActions[call.ID] = fingerprint
					}
				}
			}
		}
		if role == "tool" {
			var callID string
			if json.Unmarshal(message["tool_call_id"], &callID) != nil {
				continue
			}
			action, ok := pendingActions[callID]
			if !ok {
				continue
			}
			result, ok := openAIRepeatTextFingerprint(strings.Join(openAIRepeatPlainTextParts(message["content"]), "\n"))
			if ok {
				builder.recordCompletedAction(action, result)
			}
		}
	}
	return builder.snapshot(), nil
}

func openAIRepeatArgumentsValue(raw json.RawMessage) json.RawMessage {
	var value string
	if json.Unmarshal(raw, &value) == nil {
		return json.RawMessage(value)
	}
	return raw
}

func openAIRepeatPlainTextParts(raw json.RawMessage) []string {
	var plain string
	if json.Unmarshal(raw, &plain) == nil {
		return []string{plain}
	}
	var parts []json.RawMessage
	if json.Unmarshal(raw, &parts) != nil {
		return nil
	}
	values := make([]string, 0, len(parts))
	for _, rawPart := range parts {
		var part map[string]json.RawMessage
		if json.Unmarshal(rawPart, &part) != nil {
			continue
		}
		var partType string
		if json.Unmarshal(part["type"], &partType) != nil {
			continue
		}
		switch partType {
		case "text", "input_text", "output_text":
		default:
			continue
		}
		var text string
		if json.Unmarshal(part["text"], &text) == nil {
			values = append(values, text)
		}
	}
	return values
}

func promptFromMessages(messages []chatMessage) string {
	var b strings.Builder
	for _, msg := range messages {
		content := strings.TrimSpace(msg.Content)
		if content == "" {
			continue
		}
		role := strings.TrimSpace(msg.Role)
		if role == "" {
			role = "user"
		}
		if b.Len() > 0 {
			b.WriteString("\n")
		}
		b.WriteString(role)
		b.WriteString(": ")
		b.WriteString(content)
	}
	return b.String()
}
