package openai

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"
)

const (
	anthropicVersionHeader    = "Anthropic-Version"
	anthropicBetaHeader       = "Anthropic-Beta"
	anthropicSupportedVersion = "2023-06-01"
	anthropicModelCreatedAt   = "1970-01-01T00:00:00Z"
)

var supportedAnthropicBetas = map[string]struct{}{
	"advanced-tool-use-2025-11-20":           {},
	"advisor-tool-2026-03-01":                {},
	"claude-code-20250219":                   {},
	"context-management-2025-06-27":          {},
	"effort-2025-11-24":                      {},
	"fine-grained-tool-streaming-2025-05-14": {},
	"interleaved-thinking-2025-05-14":        {},
	"mid-conversation-system-2026-04-07":     {},
	"prompt-caching-2024-07-31":              {},
	"prompt-caching-scope-2026-01-05":        {},
	"redact-thinking-2026-02-12":             {},
	"structured-outputs-2025-12-15":          {},
	"thinking-token-count-2026-05-13":        {},
}

type anthropicRequestEnvelope struct {
	Model  string `json:"model"`
	Stream bool   `json:"stream,omitempty"`
}

type anthropicMessageRequest struct {
	Model             string                   `json:"model"`
	MaxTokens         *int                     `json:"max_tokens"`
	Messages          []anthropicInputMessage  `json:"messages"`
	System            json.RawMessage          `json:"system,omitempty"`
	Stream            bool                     `json:"stream,omitempty"`
	Temperature       *float64                 `json:"temperature,omitempty"`
	TopP              *float64                 `json:"top_p,omitempty"`
	TopK              *int                     `json:"top_k,omitempty"`
	StopSequences     []string                 `json:"stop_sequences,omitempty"`
	Tools             []anthropicTool          `json:"tools,omitempty"`
	ToolChoice        *anthropicToolChoice     `json:"tool_choice,omitempty"`
	Thinking          *anthropicThinkingConfig `json:"thinking,omitempty"`
	OutputConfig      *anthropicOutputConfig   `json:"output_config,omitempty"`
	Metadata          json.RawMessage          `json:"metadata,omitempty"`
	ContextManagement json.RawMessage          `json:"context_management,omitempty"`
}

type anthropicInputMessage struct {
	Role    string          `json:"role"`
	Content json.RawMessage `json:"content"`
}

type anthropicTool struct {
	Name         string          `json:"name"`
	Description  string          `json:"description,omitempty"`
	InputSchema  json.RawMessage `json:"input_schema"`
	DeferLoading bool            `json:"defer_loading,omitempty"`
	CacheControl json.RawMessage `json:"cache_control,omitempty"`
}

type anthropicToolChoice struct {
	Type                   string `json:"type"`
	Name                   string `json:"name,omitempty"`
	DisableParallelToolUse bool   `json:"disable_parallel_tool_use,omitempty"`
}

type anthropicThinkingConfig struct {
	Type         string `json:"type"`
	BudgetTokens int    `json:"budget_tokens,omitempty"`
	Display      string `json:"display,omitempty"`
}

type anthropicOutputConfig struct {
	Effort string                 `json:"effort,omitempty"`
	Format *anthropicOutputFormat `json:"format,omitempty"`
}

type anthropicOutputFormat struct {
	Type   string          `json:"type"`
	Schema json.RawMessage `json:"schema"`
}

type anthropicContentBlock struct {
	Type      string
	Text      string
	Thinking  string
	Signature string
	ID        string
	Name      string
	Input     json.RawMessage
	ToolUseID string
	Content   json.RawMessage
	IsError   bool
	Source    *anthropicImageSource
}

type anthropicImageSource struct {
	Type      string `json:"type"`
	MediaType string `json:"media_type,omitempty"`
	Data      string `json:"data,omitempty"`
	URL       string `json:"url,omitempty"`
}

type anthropicUsage struct {
	InputTokens              int `json:"input_tokens"`
	OutputTokens             int `json:"output_tokens"`
	CacheCreationInputTokens int `json:"cache_creation_input_tokens,omitempty"`
	CacheReadInputTokens     int `json:"cache_read_input_tokens,omitempty"`
}

type anthropicMessageResponse struct {
	ID           string           `json:"id"`
	Type         string           `json:"type"`
	Role         string           `json:"role"`
	Model        string           `json:"model"`
	Content      []map[string]any `json:"content"`
	StopReason   *string          `json:"stop_reason"`
	StopSequence *string          `json:"stop_sequence"`
	Usage        anthropicUsage   `json:"usage"`
}

func isAnthropicRequest(r *http.Request) bool {
	if r == nil {
		return false
	}
	path := r.URL.Path
	if path == "/v1/messages" || path == "/v1/messages/count_tokens" ||
		strings.HasPrefix(path, "/anthropic/v1/") {
		return true
	}
	return path == "/v1/models" && strings.TrimSpace(r.Header.Get(anthropicVersionHeader)) != ""
}

func writeAnthropicError(w http.ResponseWriter, status int, errorType, message string) {
	writeJSON(w, status, anthropicErrorResponse{
		Type:  "error",
		Error: errorBody{Type: errorType, Message: message},
	})
}

func writeAnthropicModels(w http.ResponseWriter, models []advertisedModel) {
	data := make([]anthropicModel, 0, len(models))
	for _, model := range models {
		data = append(data, anthropicModel{
			ID:          model.ID,
			CreatedAt:   anthropicModelCreatedAt,
			DisplayName: model.DisplayName,
			Type:        "model",
		})
	}
	var firstID, lastID *string
	if len(data) > 0 {
		first, last := data[0].ID, data[len(data)-1].ID
		firstID, lastID = &first, &last
	}
	writeJSON(w, http.StatusOK, anthropicModelsResponse{
		Data: data, HasMore: false, FirstID: firstID, LastID: lastID,
	})
}

func validateAnthropicHeaders(r *http.Request) error {
	version := strings.TrimSpace(r.Header.Get(anthropicVersionHeader))
	if version == "" {
		return fmt.Errorf("%s header is required", strings.ToLower(anthropicVersionHeader))
	}
	if version != anthropicSupportedVersion {
		return fmt.Errorf("unsupported anthropic-version %q", version)
	}
	if _, err := anthropicBetaValues(r.Header.Values(anthropicBetaHeader)); err != nil {
		return err
	}
	return nil
}

func anthropicBetaValues(values []string) ([]string, error) {
	var out []string
	for _, value := range values {
		for _, beta := range strings.Split(value, ",") {
			beta = strings.TrimSpace(beta)
			if beta == "" {
				continue
			}
			if _, ok := supportedAnthropicBetas[beta]; !ok {
				return nil, fmt.Errorf("unsupported anthropic-beta %q", beta)
			}
			out = append(out, beta)
		}
	}
	return out, nil
}

func decodeAnthropicEnvelope(body []byte) (anthropicRequestEnvelope, error) {
	if !json.Valid(body) {
		return anthropicRequestEnvelope{}, fmt.Errorf("invalid JSON request")
	}
	var envelope anthropicRequestEnvelope
	if err := json.Unmarshal(body, &envelope); err != nil {
		return anthropicRequestEnvelope{}, fmt.Errorf("decode Messages request: %w", err)
	}
	if strings.TrimSpace(envelope.Model) == "" {
		return anthropicRequestEnvelope{}, fmt.Errorf("model is required")
	}
	return envelope, nil
}

func decodeAnthropicMessageRequest(body []byte, requireMaxTokens bool) (anthropicMessageRequest, error) {
	var req anthropicMessageRequest
	if err := decodeStrictJSON(body, &req); err != nil {
		return req, fmt.Errorf("decode Messages request: %w", err)
	}
	if err := validateAnthropicOutputEffort(body); err != nil {
		return req, err
	}
	if strings.TrimSpace(req.Model) == "" {
		return req, fmt.Errorf("model is required")
	}
	if requireMaxTokens && req.MaxTokens == nil {
		return req, fmt.Errorf("max_tokens is required")
	}
	if req.MaxTokens != nil && *req.MaxTokens <= 0 {
		return req, fmt.Errorf("max_tokens must be positive")
	}
	if len(req.Messages) == 0 {
		return req, fmt.Errorf("messages are required")
	}
	if req.Temperature != nil && (*req.Temperature < 0 || *req.Temperature > 1) {
		return req, fmt.Errorf("temperature must be between 0 and 1")
	}
	if req.TopP != nil && (*req.TopP < 0 || *req.TopP > 1) {
		return req, fmt.Errorf("top_p must be between 0 and 1")
	}
	if req.TopK != nil && *req.TopK <= 0 {
		return req, fmt.Errorf("top_k must be positive")
	}
	contextManagement := bytes.TrimSpace(req.ContextManagement)
	if len(contextManagement) > 0 && !bytes.Equal(contextManagement, []byte("null")) &&
		(!json.Valid(contextManagement) || contextManagement[0] != '{') {
		return req, fmt.Errorf("context_management must be an object")
	}
	for index, stop := range req.StopSequences {
		if stop == "" {
			return req, fmt.Errorf("stop_sequences[%d] must not be empty", index)
		}
	}
	for index, message := range req.Messages {
		if message.Role != "user" && message.Role != "assistant" && message.Role != "system" {
			return req, fmt.Errorf("messages[%d].role must be user, assistant, or system", index)
		}
		blocks, err := decodeAnthropicContent(message.Content)
		if err != nil {
			return req, fmt.Errorf("messages[%d].content: %w", index, err)
		}
		if message.Role == "system" {
			for blockIndex, block := range blocks {
				if block.Type != "text" {
					return req, fmt.Errorf("messages[%d].content[%d] must be text for a system message", index, blockIndex)
				}
			}
		}
	}
	if err := validateAnthropicMidConversationSystemPlacement(req.Messages); err != nil {
		return req, err
	}
	if _, err := decodeAnthropicSystem(req.System); err != nil {
		return req, err
	}
	for index, tool := range req.Tools {
		if strings.TrimSpace(tool.Name) == "" {
			return req, fmt.Errorf("tools[%d].name is required", index)
		}
		if len(tool.InputSchema) == 0 || !json.Valid(tool.InputSchema) || tool.InputSchema[0] != '{' {
			return req, fmt.Errorf("tools[%d].input_schema must be an object", index)
		}
	}
	if err := validateAnthropicToolChoice(req.ToolChoice); err != nil {
		return req, err
	}
	if req.Thinking != nil {
		switch req.Thinking.Display {
		case "", "omitted", "summarized":
		default:
			return req, fmt.Errorf("thinking.display must be omitted or summarized")
		}
		switch req.Thinking.Type {
		case "adaptive":
			if req.Thinking.BudgetTokens != 0 {
				return req, fmt.Errorf("adaptive thinking does not accept budget_tokens")
			}
		case "enabled":
			if req.Thinking.BudgetTokens <= 0 {
				return req, fmt.Errorf("thinking must be enabled with a positive budget_tokens")
			}
		default:
			return req, fmt.Errorf("thinking must be enabled with a positive budget_tokens")
		}
	}
	if req.OutputConfig != nil {
		if format := req.OutputConfig.Format; format != nil {
			if format.Type != "json_schema" {
				return req, fmt.Errorf("output_config.format.type must be json_schema")
			}
			trimmed := bytes.TrimSpace(format.Schema)
			if len(trimmed) == 0 || trimmed[0] != '{' || !json.Valid(trimmed) {
				return req, fmt.Errorf("output_config.format.schema must be an object")
			}
		}
	}
	return req, nil
}

func validateAnthropicMidConversationSystemPlacement(messages []anthropicInputMessage) error {
	for index, message := range messages {
		if message.Role != "system" {
			continue
		}
		if index == 0 {
			return fmt.Errorf("messages[0].role system must use the top-level system field")
		}
		previousRole := messages[index-1].Role
		if previousRole != "user" && previousRole != "system" {
			return fmt.Errorf("messages[%d].role system must follow a user message", index)
		}
		if index+1 < len(messages) {
			nextRole := messages[index+1].Role
			if nextRole != "assistant" && nextRole != "system" {
				return fmt.Errorf("messages[%d].role system must precede an assistant message or end the message list", index)
			}
		}
	}
	return nil
}

// validateAnthropicOutputEffort validates the caller's raw nested effort value
// without re-encoding the request. Native provider tunnels retain every field
// and byte outside the existing top-level model replacement.
func validateAnthropicOutputEffort(body []byte) error {
	rootFields, _, err := scanTopLevelJSONObject(body)
	if err != nil {
		return fmt.Errorf("decode Messages request: %w", err)
	}
	outputConfig, present, err := anthropicExactJSONMember(rootFields, body, "output_config")
	if err != nil {
		return anthropicEffortError()
	}
	if !present || bytes.Equal(bytes.TrimSpace(outputConfig), []byte("null")) {
		return nil
	}
	configFields, _, err := scanTopLevelJSONObject(outputConfig)
	if err != nil {
		return anthropicEffortError()
	}
	rawEffort, present, err := anthropicExactJSONMember(configFields, outputConfig, "effort")
	if err != nil {
		return anthropicEffortError()
	}
	if !present {
		return nil
	}
	var effort string
	if err := json.Unmarshal(rawEffort, &effort); err != nil {
		return anthropicEffortError()
	}
	switch effort {
	case "low", "medium", "high", "xhigh", "max":
		return nil
	default:
		return anthropicEffortError()
	}
}

func anthropicExactJSONMember(fields []topLevelJSONFieldSpan, body []byte, name string) ([]byte, bool, error) {
	var value []byte
	for _, field := range fields {
		if !strings.EqualFold(field.name, name) {
			continue
		}
		if field.name != name || value != nil {
			return nil, false, fmt.Errorf("non-canonical or duplicate %s member", name)
		}
		value = body[field.valueFrom:field.valueTo]
	}
	return value, value != nil, nil
}

func anthropicEffortError() error {
	return fmt.Errorf("output_config.effort must be low, medium, high, xhigh, or max")
}

func decodeStrictJSON(body []byte, dst any) error {
	decoder := json.NewDecoder(bytes.NewReader(body))
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(dst); err != nil {
		return err
	}
	if err := decoder.Decode(&struct{}{}); err != io.EOF {
		if err == nil {
			return fmt.Errorf("multiple JSON values are not allowed")
		}
		return err
	}
	return nil
}

func decodeAnthropicSystem(raw json.RawMessage) ([]anthropicContentBlock, error) {
	if len(bytes.TrimSpace(raw)) == 0 || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) {
		return nil, nil
	}
	var text string
	if err := json.Unmarshal(raw, &text); err == nil {
		return []anthropicContentBlock{{Type: "text", Text: text}}, nil
	}
	blocks, err := decodeAnthropicContent(raw)
	if err != nil {
		return nil, fmt.Errorf("system: %w", err)
	}
	for _, block := range blocks {
		if block.Type != "text" {
			return nil, fmt.Errorf("system supports only text blocks")
		}
	}
	return blocks, nil
}

func decodeAnthropicContent(raw json.RawMessage) ([]anthropicContentBlock, error) {
	if len(bytes.TrimSpace(raw)) == 0 {
		return nil, fmt.Errorf("content is required")
	}
	var text string
	if err := json.Unmarshal(raw, &text); err == nil {
		return []anthropicContentBlock{{Type: "text", Text: text}}, nil
	}
	var items []json.RawMessage
	if err := json.Unmarshal(raw, &items); err != nil {
		return nil, fmt.Errorf("content must be a string or array")
	}
	blocks := make([]anthropicContentBlock, 0, len(items))
	for index, item := range items {
		block, err := decodeAnthropicContentBlock(item)
		if err != nil {
			return nil, fmt.Errorf("block %d: %w", index, err)
		}
		blocks = append(blocks, block)
	}
	return blocks, nil
}

func decodeAnthropicContentBlock(raw json.RawMessage) (anthropicContentBlock, error) {
	var kind struct {
		Type string `json:"type"`
	}
	if err := json.Unmarshal(raw, &kind); err != nil || strings.TrimSpace(kind.Type) == "" {
		return anthropicContentBlock{}, fmt.Errorf("content block type is required")
	}
	switch kind.Type {
	case "text":
		var block struct {
			Type         string          `json:"type"`
			Text         string          `json:"text"`
			CacheControl json.RawMessage `json:"cache_control,omitempty"`
		}
		if err := decodeStrictJSON(raw, &block); err != nil {
			return anthropicContentBlock{}, err
		}
		return anthropicContentBlock{Type: block.Type, Text: block.Text}, nil
	case "image":
		var block struct {
			Type         string               `json:"type"`
			Source       anthropicImageSource `json:"source"`
			CacheControl json.RawMessage      `json:"cache_control,omitempty"`
		}
		if err := decodeStrictJSON(raw, &block); err != nil {
			return anthropicContentBlock{}, err
		}
		if block.Source.Type == "base64" {
			if block.Source.MediaType == "" || block.Source.Data == "" {
				return anthropicContentBlock{}, fmt.Errorf("base64 image source requires media_type and data")
			}
		} else if block.Source.Type == "url" {
			if block.Source.URL == "" {
				return anthropicContentBlock{}, fmt.Errorf("URL image source requires url")
			}
		} else {
			return anthropicContentBlock{}, fmt.Errorf("unsupported image source type %q", block.Source.Type)
		}
		return anthropicContentBlock{Type: block.Type, Source: &block.Source}, nil
	case "tool_use":
		var block struct {
			Type         string          `json:"type"`
			ID           string          `json:"id"`
			Name         string          `json:"name"`
			Input        json.RawMessage `json:"input"`
			CacheControl json.RawMessage `json:"cache_control,omitempty"`
		}
		if err := decodeStrictJSON(raw, &block); err != nil {
			return anthropicContentBlock{}, err
		}
		if block.ID == "" || block.Name == "" || len(block.Input) == 0 || !json.Valid(block.Input) {
			return anthropicContentBlock{}, fmt.Errorf("tool_use requires id, name, and valid input")
		}
		return anthropicContentBlock{Type: block.Type, ID: block.ID, Name: block.Name, Input: block.Input}, nil
	case "tool_result":
		var block struct {
			Type         string          `json:"type"`
			ToolUseID    string          `json:"tool_use_id"`
			Content      json.RawMessage `json:"content,omitempty"`
			IsError      bool            `json:"is_error,omitempty"`
			CacheControl json.RawMessage `json:"cache_control,omitempty"`
		}
		if err := decodeStrictJSON(raw, &block); err != nil {
			return anthropicContentBlock{}, err
		}
		if block.ToolUseID == "" {
			return anthropicContentBlock{}, fmt.Errorf("tool_result requires tool_use_id")
		}
		return anthropicContentBlock{Type: block.Type, ToolUseID: block.ToolUseID, Content: block.Content, IsError: block.IsError}, nil
	case "thinking":
		var block struct {
			Type         string          `json:"type"`
			Thinking     string          `json:"thinking"`
			Signature    string          `json:"signature,omitempty"`
			CacheControl json.RawMessage `json:"cache_control,omitempty"`
		}
		if err := decodeStrictJSON(raw, &block); err != nil {
			return anthropicContentBlock{}, err
		}
		return anthropicContentBlock{Type: block.Type, Thinking: block.Thinking, Signature: block.Signature}, nil
	default:
		return anthropicContentBlock{}, fmt.Errorf("unsupported content block type %q", kind.Type)
	}
}

func validateAnthropicToolChoice(choice *anthropicToolChoice) error {
	if choice == nil {
		return nil
	}
	switch choice.Type {
	case "auto", "any", "none":
		if choice.Name != "" {
			return fmt.Errorf("tool_choice.name is only valid for type tool")
		}
	case "tool":
		if strings.TrimSpace(choice.Name) == "" {
			return fmt.Errorf("tool_choice type tool requires name")
		}
	default:
		return fmt.Errorf("unsupported tool_choice type %q", choice.Type)
	}
	return nil
}

func anthropicRequestNeedsTools(body []byte) bool {
	var request struct {
		Tools    []json.RawMessage `json:"tools"`
		Messages []struct {
			Content json.RawMessage `json:"content"`
		} `json:"messages"`
	}
	if json.Unmarshal(body, &request) != nil || len(request.Tools) > 0 {
		return len(request.Tools) > 0
	}
	for _, message := range request.Messages {
		var blocks []json.RawMessage
		if json.Unmarshal(message.Content, &blocks) != nil {
			continue
		}
		for _, raw := range blocks {
			var block struct {
				Type string `json:"type"`
			}
			if json.Unmarshal(raw, &block) == nil && (block.Type == "tool_use" || block.Type == "tool_result") {
				return true
			}
		}
	}
	return false
}
