package provider

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

// CanAdaptResponses reports whether responses ingress can be served via chat completions.
func CanAdaptResponses(allowsResponses, allowsChat bool) bool {
	return allowsChat && !allowsResponses
}

// ResponsesToChatCompletion converts an OpenAI Responses API request to chat completions format.
func ResponsesToChatCompletion(body []byte) ([]byte, error) {
	var req map[string]json.RawMessage
	if err := json.Unmarshal(body, &req); err != nil {
		return nil, fmt.Errorf("invalid json: %w", err)
	}

	out := make(map[string]any)
	if raw, ok := req["model"]; ok {
		var model string
		if err := json.Unmarshal(raw, &model); err != nil || model == "" {
			return nil, fmt.Errorf("model is required")
		}
		out["model"] = model
	}

	messages := make([]map[string]any, 0, 4)
	if raw, ok := req["instructions"]; ok {
		var instructions string
		if err := json.Unmarshal(raw, &instructions); err == nil && instructions != "" {
			messages = append(messages, map[string]any{"role": "system", "content": instructions})
		} else {
			var instructionParts []json.RawMessage
			if err := json.Unmarshal(raw, &instructionParts); err == nil {
				if text := joinContentPartTexts(instructionParts); text != "" {
					messages = append(messages, map[string]any{"role": "system", "content": text})
				}
			}
		}
	}
	if raw, ok := req["input"]; ok {
		inputMsgs, err := parseResponsesInput(raw)
		if err != nil {
			return nil, err
		}
		messages = append(messages, inputMsgs...)
	}
	if len(messages) == 0 {
		return nil, fmt.Errorf("input is required")
	}
	out["messages"] = messages

	copyField(req, out, "stream")
	copyField(req, out, "temperature")
	copyField(req, out, "top_p")
	copyField(req, out, "user")
	copyField(req, out, "stop")
	copyField(req, out, "tools")
	copyField(req, out, "tool_choice")
	copyField(req, out, "parallel_tool_calls")
	copyField(req, out, "seed")
	copyField(req, out, "n")
	copyField(req, out, "presence_penalty")
	copyField(req, out, "frequency_penalty")
	copyField(req, out, "logit_bias")
	copyField(req, out, "response_format")

	if raw, ok := req["max_output_tokens"]; ok {
		var v int
		if err := json.Unmarshal(raw, &v); err == nil && v > 0 {
			out["max_tokens"] = v
		}
	} else {
		copyField(req, out, "max_tokens")
	}

	if raw, ok := req["text"]; ok {
		if rf := responsesTextToResponseFormat(raw); rf != nil {
			out["response_format"] = rf
		}
	}

	return json.Marshal(out)
}

func copyField(src map[string]json.RawMessage, dst map[string]any, key string) {
	if raw, ok := src[key]; ok && len(raw) > 0 && string(raw) != "null" {
		var v any
		if json.Unmarshal(raw, &v) == nil {
			dst[key] = v
		}
	}
}

func responsesTextToResponseFormat(raw json.RawMessage) any {
	var text struct {
		Format struct {
			Type string `json:"type"`
		} `json:"format"`
	}
	if err := json.Unmarshal(raw, &text); err != nil || text.Format.Type == "" {
		return nil
	}
	switch text.Format.Type {
	case "json_object":
		return map[string]any{"type": "json_object"}
	case "text":
		return map[string]any{"type": "text"}
	default:
		return map[string]any{"type": text.Format.Type}
	}
}

func parseResponsesInput(raw json.RawMessage) ([]map[string]any, error) {
	var asString string
	if err := json.Unmarshal(raw, &asString); err == nil {
		if asString == "" {
			return nil, fmt.Errorf("input is required")
		}
		return []map[string]any{{"role": "user", "content": asString}}, nil
	}

	var asArray []json.RawMessage
	if err := json.Unmarshal(raw, &asArray); err != nil {
		return nil, fmt.Errorf("invalid input")
	}
	out := make([]map[string]any, 0, len(asArray))
	for _, item := range asArray {
		msg, err := parseResponsesInputItem(item)
		if err != nil {
			return nil, err
		}
		out = append(out, msg)
	}
	return out, nil
}

func parseResponsesInputItem(raw json.RawMessage) (map[string]any, error) {
	var asString string
	if err := json.Unmarshal(raw, &asString); err == nil {
		return map[string]any{"role": "user", "content": asString}, nil
	}

	var item map[string]json.RawMessage
	if err := json.Unmarshal(raw, &item); err != nil {
		return nil, fmt.Errorf("invalid input item")
	}

	itemType := ""
	if rawType, ok := item["type"]; ok {
		_ = json.Unmarshal(rawType, &itemType)
	}

	role := "user"
	if rawRole, ok := item["role"]; ok {
		_ = json.Unmarshal(rawRole, &role)
	}
	if role == "" {
		role = "user"
	}
	role = normalizeResponsesRole(role)

	switch itemType {
	case "message":
		if rawContent, ok := item["content"]; ok {
			content, err := parseResponsesContent(rawContent)
			if err != nil {
				return nil, err
			}
			return map[string]any{"role": role, "content": content}, nil
		}
	case "input_text":
		var text string
		if rawText, ok := item["text"]; ok {
			_ = json.Unmarshal(rawText, &text)
		}
		if text != "" {
			return map[string]any{"role": role, "content": text}, nil
		}
	case "function_call_output":
		return parseFunctionCallOutputItem(item, role)
	}

	if rawContent, ok := item["content"]; ok {
		content, err := parseResponsesContent(rawContent)
		if err != nil {
			return nil, err
		}
		return map[string]any{"role": role, "content": content}, nil
	}
	if rawText, ok := item["text"]; ok {
		var text string
		if err := json.Unmarshal(rawText, &text); err == nil {
			return map[string]any{"role": role, "content": text}, nil
		}
	}
	if rawInput, ok := item["input"]; ok {
		var text string
		if err := json.Unmarshal(rawInput, &text); err == nil {
			return map[string]any{"role": role, "content": text}, nil
		}
	}
	return nil, fmt.Errorf("unsupported input item")
}

func normalizeResponsesRole(role string) string {
	switch role {
	case "developer":
		return "system"
	default:
		return role
	}
}

func parseResponsesContent(raw json.RawMessage) (any, error) {
	var asString string
	if err := json.Unmarshal(raw, &asString); err == nil {
		return asString, nil
	}
	var parts []json.RawMessage
	if err := json.Unmarshal(raw, &parts); err != nil {
		return nil, fmt.Errorf("invalid content")
	}
	converted := make([]any, 0, len(parts))
	for _, part := range parts {
		if item, ok := convertResponsesContentPart(part); ok {
			converted = append(converted, item)
		}
	}
	if len(converted) == 0 {
		return "", nil
	}
	if len(converted) == 1 {
		if text, ok := converted[0].(string); ok {
			return text, nil
		}
		if m, ok := converted[0].(map[string]any); ok {
			if m["type"] == "text" {
				if text, ok := m["text"].(string); ok {
					return text, nil
				}
			}
		}
	}
	return converted, nil
}

func convertResponsesContentPart(raw json.RawMessage) (any, bool) {
	var asString string
	if err := json.Unmarshal(raw, &asString); err == nil {
		return asString, true
	}
	var part map[string]json.RawMessage
	if err := json.Unmarshal(raw, &part); err != nil {
		return nil, false
	}
	typ := ""
	if rawType, ok := part["type"]; ok {
		_ = json.Unmarshal(rawType, &typ)
	}
	switch typ {
	case "input_text", "text", "output_text":
		var text string
		if rawText, ok := part["text"]; ok {
			_ = json.Unmarshal(rawText, &text)
		}
		if text == "" {
			return nil, false
		}
		return map[string]any{"type": "text", "text": text}, true
	case "input_image":
		out := map[string]any{"type": "image_url", "image_url": map[string]any{}}
		img := out["image_url"].(map[string]any)
		if rawURL, ok := part["image_url"]; ok {
			var url string
			if err := json.Unmarshal(rawURL, &url); err == nil {
				img["url"] = url
			} else {
				var nested map[string]any
				if err := json.Unmarshal(rawURL, &nested); err == nil {
					for k, v := range nested {
						img[k] = v
					}
				}
			}
		}
		if rawDetail, ok := part["detail"]; ok {
			var detail string
			if json.Unmarshal(rawDetail, &detail) == nil {
				img["detail"] = detail
			}
		}
		if img["url"] == nil {
			return nil, false
		}
		return out, true
	case "image_url":
		var v any
		_ = json.Unmarshal(raw, &v)
		return v, true
	case "input_file":
		var fileID, filename string
		if rawID, ok := part["file_id"]; ok {
			_ = json.Unmarshal(rawID, &fileID)
		}
		if rawName, ok := part["filename"]; ok {
			_ = json.Unmarshal(rawName, &filename)
		}
		label := filename
		if label == "" {
			label = fileID
		}
		if label == "" {
			return nil, false
		}
		return map[string]any{"type": "text", "text": "[file:" + label + "]"}, true
	default:
		return nil, false
	}
}

func joinContentPartTexts(parts []json.RawMessage) string {
	var texts []string
	for _, part := range parts {
		if item, ok := convertResponsesContentPart(part); ok {
			if text, ok := item.(string); ok {
				texts = append(texts, text)
			} else if m, ok := item.(map[string]any); ok && m["type"] == "text" {
				if text, ok := m["text"].(string); ok {
					texts = append(texts, text)
				}
			}
		}
	}
	return strings.Join(texts, "\n")
}

func parseFunctionCallOutputItem(item map[string]json.RawMessage, role string) (map[string]any, error) {
	var output, callID string
	if rawOutput, ok := item["output"]; ok {
		_ = json.Unmarshal(rawOutput, &output)
	}
	if rawCallID, ok := item["call_id"]; ok {
		_ = json.Unmarshal(rawCallID, &callID)
	}
	if output == "" {
		return nil, fmt.Errorf("unsupported input item")
	}
	msg := map[string]any{"role": "tool", "content": output}
	if callID != "" {
		msg["tool_call_id"] = callID
	}
	if role != "" && role != "user" {
		msg["role"] = role
	}
	return msg, nil
}

// ChatCompletionToResponses converts a chat completions response to Responses API format.
func ChatCompletionToResponses(body []byte) ([]byte, error) {
	var chat map[string]any
	if err := json.Unmarshal(body, &chat); err != nil {
		return nil, err
	}
	if _, ok := chat["error"]; ok {
		return body, nil
	}

	content := extractChatContent(chat)
	chatID, _ := chat["id"].(string)
	model, _ := chat["model"].(string)
	created := extractCreatedAt(chat)
	respID := responsesIDFromChatID(chatID)
	msgID := messageIDFromResponseID(respID)
	usage := chatUsageToResponses(chat["usage"])

	resp := buildResponseObject(respID, msgID, model, created, content, usage, "completed")
	return json.Marshal(resp)
}

func extractCreatedAt(chat map[string]any) int64 {
	switch v := chat["created"].(type) {
	case float64:
		return int64(v)
	case int64:
		return v
	case int:
		return int64(v)
	default:
		return 0
	}
}

func responsesIDFromChatID(chatID string) string {
	if chatID == "" {
		return "resp_adapted"
	}
	if strings.HasPrefix(chatID, "resp_") {
		return chatID
	}
	if strings.HasPrefix(chatID, "chatcmpl-") {
		return "resp_" + strings.TrimPrefix(chatID, "chatcmpl-")
	}
	return "resp_" + chatID
}

func messageIDFromResponseID(respID string) string {
	if strings.HasPrefix(respID, "resp_") {
		return "msg_" + strings.TrimPrefix(respID, "resp_")
	}
	return "msg_" + respID
}

func buildOutputTextContent(text string) map[string]any {
	return map[string]any{
		"type":        "output_text",
		"text":        text,
		"annotations": []any{},
	}
}

func buildOutputMessage(msgID, content, status string) map[string]any {
	return map[string]any{
		"type":    "message",
		"id":      msgID,
		"role":    "assistant",
		"status":  status,
		"content": []map[string]any{buildOutputTextContent(content)},
	}
}

func buildResponseObject(respID, msgID, model string, created int64, content string, usage map[string]any, status string) map[string]any {
	msgStatus := status
	if status == "in_progress" || status == "queued" {
		msgStatus = "in_progress"
	}
	resp := map[string]any{
		"id":                   respID,
		"object":               "response",
		"created_at":           created,
		"status":               status,
		"model":                model,
		"output_text":          content,
		"error":                nil,
		"incomplete_details":   nil,
		"parallel_tool_calls":  true,
		"output":               []map[string]any{buildOutputMessage(msgID, content, msgStatus)},
	}
	if usage != nil {
		resp["usage"] = usage
	}
	return resp
}

func extractChatContent(chat map[string]any) string {
	choices, ok := chat["choices"].([]any)
	if !ok || len(choices) == 0 {
		return ""
	}
	choice, ok := choices[0].(map[string]any)
	if !ok {
		return ""
	}
	if msg, ok := choice["message"].(map[string]any); ok {
		if content, ok := msg["content"].(string); ok {
			return content
		}
	}
	if delta, ok := choice["delta"].(map[string]any); ok {
		if content, ok := delta["content"].(string); ok {
			return content
		}
	}
	return ""
}

func chatUsageToResponses(usage any) map[string]any {
	u, ok := usage.(map[string]any)
	if !ok {
		return nil
	}
	in, hasIn := numberAsInt(u["prompt_tokens"])
	out, hasOut := numberAsInt(u["completion_tokens"])
	total, hasTotal := numberAsInt(u["total_tokens"])
	if !hasIn && !hasOut && !hasTotal {
		return nil
	}
	if !hasTotal && hasIn && hasOut {
		total = in + out
	}
	return map[string]any{
		"input_tokens":  in,
		"output_tokens": out,
		"total_tokens":  total,
		"input_tokens_details": map[string]any{
			"cached_tokens": 0,
		},
		"output_tokens_details": map[string]any{
			"reasoning_tokens": 0,
		},
	}
}

func numberAsInt(v any) (int, bool) {
	switch n := v.(type) {
	case float64:
		return int(n), true
	case int:
		return n, true
	case int64:
		return int(n), true
	default:
		return 0, false
	}
}

// AdaptResponsesStream wraps a chat-completions SSE stream as Responses API SSE events.
func AdaptResponsesStream(chatBody []byte, upstream io.Reader, w io.Writer, flusher http.Flusher) (*StreamResult, error) {
	var req map[string]any
	_ = json.Unmarshal(chatBody, &req)
	model, _ := req["model"].(string)

	result := &StreamResult{StatusCode: http.StatusOK}
	respID := "resp_adapted"
	msgID := "msg_adapted"
	seq := 0
	var fullText string
	var usage map[string]any
	started := false

	writeEvent := func(eventType string, payload map[string]any) error {
		payload["type"] = eventType
		payload["sequence_number"] = seq
		seq++
		b, err := json.Marshal(payload)
		if err != nil {
			return err
		}
		if _, err := fmt.Fprintf(w, "event: %s\ndata: %s\n\n", eventType, b); err != nil {
			return err
		}
		if flusher != nil {
			flusher.Flush()
		}
		return nil
	}

	emitCreated := func(status string) error {
		response := buildResponseObject(respID, msgID, model, 0, "", nil, status)
		response["output"] = []any{}
		response["output_text"] = ""
		return writeEvent("response.created", map[string]any{"response": response})
	}

	scanner := bufio.NewScanner(upstream)
	scanner.Buffer(make([]byte, 64*1024), 1024*1024)
	for scanner.Scan() {
		line := scanner.Text()
		if !strings.HasPrefix(line, "data: ") {
			continue
		}
		data := strings.TrimPrefix(line, "data: ")
		if data == "[DONE]" {
			break
		}
		tokensIn, tokensOut := parseStreamChunk([]byte(data))
		result.TokensIn += tokensIn
		result.TokensOut += tokensOut

		var chunk map[string]any
		if err := json.Unmarshal([]byte(data), &chunk); err != nil {
			continue
		}
		if id, ok := chunk["id"].(string); ok && id != "" {
			respID = responsesIDFromChatID(id)
			msgID = messageIDFromResponseID(respID)
		}
		if m, ok := chunk["model"].(string); ok && m != "" {
			model = m
		}
		if u := chatUsageToResponses(chunk["usage"]); u != nil {
			usage = u
		}

		delta := extractChatContent(chunk)
		if delta == "" {
			continue
		}

		if !started {
			started = true
			if err := emitCreated("in_progress"); err != nil {
				return result, err
			}
			if err := writeEvent("response.in_progress", map[string]any{
				"response": buildResponseObject(respID, msgID, model, 0, "", nil, "in_progress"),
			}); err != nil {
				return result, err
			}
			if err := writeEvent("response.output_item.added", map[string]any{
				"output_index": 0,
				"item": map[string]any{
					"type": "message", "id": msgID, "role": "assistant", "status": "in_progress", "content": []any{},
				},
			}); err != nil {
				return result, err
			}
		}

		fullText += delta
		if err := writeEvent("response.output_text.delta", map[string]any{
			"item_id": msgID, "output_index": 0, "content_index": 0, "delta": delta, "logprobs": []any{},
		}); err != nil {
			return result, err
		}
	}
	if err := scanner.Err(); err != nil {
		return result, err
	}

	if started {
		_ = writeEvent("response.output_text.done", map[string]any{
			"item_id": msgID, "output_index": 0, "content_index": 0, "text": fullText, "logprobs": []any{},
		})
		doneItem := buildOutputMessage(msgID, fullText, "completed")
		_ = writeEvent("response.output_item.done", map[string]any{
			"output_index": 0,
			"item":         doneItem,
		})
		final := buildResponseObject(respID, msgID, model, 0, fullText, usage, "completed")
		_ = writeEvent("response.completed", map[string]any{"response": final})
	}
	return result, nil
}
