package openai

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"time"

	"go.uber.org/zap"

	edgeservice "iop/apps/edge/internal/service"
)

func (s *Server) handleResponses(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed")
		return
	}
	defer r.Body.Close()

	var req responsesRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "invalid_request_error", "invalid JSON request")
		return
	}

	if req.Stream {
		writeError(w, http.StatusBadRequest, "invalid_request_error", "streaming is not supported for /v1/responses")
		return
	}

	inputStr, err := parseResponsesInput(req.Input)
	if err != nil {
		writeError(w, http.StatusBadRequest, "invalid_request_error", err.Error())
		return
	}

	prompt := buildResponsesPrompt(req.Instructions, inputStr)
	outputPolicy := s.resolveOutputPolicy(prompt)
	if instruction := strictOutputContractInstruction(outputPolicy); instruction != "" {
		prompt = instruction + "\n" + prompt
	}

	runMeta, inferenceTarget, workspace, err := parseOpenAIMetadata(req.Metadata)
	if err != nil {
		writeError(w, http.StatusBadRequest, "invalid_request_error", err.Error())
		return
	}

	dispatch, ok := s.resolveRouteDispatch(req.Model, inferenceTarget)
	if !ok {
		writeError(w, http.StatusBadRequest, "invalid_request_error", "model is required")
		return
	}
	if err := validateWorkspaceForRoute(dispatch, workspace); err != nil {
		writeError(w, http.StatusBadRequest, "invalid_request_error", err.Error())
		return
	}

	runMeta["source"] = "openai-responses"
	runMeta["openai_model"] = req.Model
	runMeta["openai_stream"] = fmt.Sprintf("%t", req.Stream)
	runMeta["strict_output"] = fmt.Sprintf("%t", outputPolicy.Strict)
	input := map[string]any{"prompt": prompt}
	if outputPolicy.Strict {
		input["think"] = false
	}

	s.logger.Info("openai responses input",
		zap.String("model", req.Model),
		zap.String("target", dispatch.Target),
		zap.String("adapter", dispatch.Adapter),
		zap.Bool("strict_output", outputPolicy.Strict),
		zap.String("xml_completion_tool", outputPolicy.XMLCompletionTool),
		zap.Bool("contract_instruction", outputPolicy.ContractInstruction),
		zap.Int("prompt_len", len(prompt)),
		zap.String("prompt_preview", previewString(prompt, 1000)),
	)

	handle, err := s.service.SubmitRun(r.Context(), edgeservice.SubmitRunRequest{
		NodeRef:    dispatch.NodeRef,
		Adapter:    dispatch.Adapter,
		Target:     dispatch.Target,
		SessionID:  dispatch.SessionID,
		Workspace:  workspace,
		Prompt:     prompt,
		Input:      input,
		TimeoutSec: dispatch.TimeoutSec,
		Metadata:   runMeta,
	})
	if err != nil {
		writeError(w, http.StatusBadGateway, "node_dispatch_error", err.Error())
		return
	}
	defer handle.Close()

	s.completeResponse(w, r, req, handle, outputPolicy)
}

func (s *Server) completeResponse(w http.ResponseWriter, r *http.Request, req responsesRequest, handle edgeservice.RunResult, outputPolicy strictOutputPolicy) {
	text, reasoning, usage, err := collectRunResult(r.Context(), handle.Stream(), handle.WaitTimeout())
	if err != nil {
		writeError(w, httpStatusForRunError(err), "run_error", err.Error())
		return
	}
	text, reasoning, normalized := normalizeCompletionOutput(outputPolicy, text, reasoning)
	s.logger.Info("openai responses output",
		zap.String("run_id", handle.Dispatch().RunID),
		zap.Bool("strict_output", outputPolicy.Strict),
		zap.String("xml_completion_tool", outputPolicy.XMLCompletionTool),
		zap.Bool("normalized", normalized),
		zap.Int("content_len", len(text)),
		zap.Int("reasoning_len", len(reasoning)),
		zap.String("content_preview", previewString(text, 1000)),
	)

	var u openAIUsage
	if usage != nil {
		u = *usage
	}

	writeJSON(w, http.StatusOK, responsesResponse{
		ID:         "resp-" + handle.Dispatch().RunID,
		Object:     "response",
		CreatedAt:  time.Now().Unix(),
		Model:      responseModel(req.Model, handle.Dispatch().Target),
		OutputText: text,
		Output: []responsesOutputItem{{
			Type: "message",
			Role: "assistant",
			Content: []responsesContentItem{{
				Type: "output_text",
				Text: text,
			}},
		}},
		Usage: u,
	})
}

func parseResponsesInput(raw json.RawMessage) (string, error) {
	if len(raw) == 0 {
		return "", fmt.Errorf("input is required")
	}
	var s string
	if err := json.Unmarshal(raw, &s); err != nil {
		return "", fmt.Errorf("input must be a string")
	}
	if strings.TrimSpace(s) == "" {
		return "", fmt.Errorf("input is required")
	}
	return s, nil
}

func buildResponsesPrompt(instructions, input string) string {
	instructions = strings.TrimSpace(instructions)
	input = strings.TrimSpace(input)
	if instructions == "" {
		return input
	}
	return instructions + "\n" + input
}

func parseOpenAIMetadata(raw json.RawMessage) (map[string]string, string, string, error) {
	if len(raw) == 0 || string(raw) == "null" {
		return make(map[string]string), "", "", nil
	}

	var rawMap map[string]json.RawMessage
	if err := json.Unmarshal(raw, &rawMap); err != nil {
		return nil, "", "", fmt.Errorf("metadata must be an object")
	}

	if _, hasCLI := rawMap["cli"]; hasCLI {
		return nil, "", "", fmt.Errorf("metadata.cli is not supported")
	}

	var meta responsesMetadata
	if err := json.Unmarshal(raw, &meta); err != nil {
		return nil, "", "", fmt.Errorf("invalid metadata format")
	}

	flat := meta.metadataForRun()
	inferenceTarget := ""
	if meta.Inference != nil {
		inferenceTarget = meta.Inference.Target
	}

	return flat, inferenceTarget, strings.TrimSpace(meta.Workspace), nil
}
