package openai

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

	"iop/packages/go/config"
)

type hotPathTurn struct {
	RequestID     string
	StageID       string
	CallID        string
	OwnerEdgeID   string
	PrincipalRef  string
	Preset        config.ExecutionPreset
	Dispatch      routeDispatch
	Protocol      string // "openai" or "anthropic"
	Stream        bool
	PublicModelID string
	Writer        http.ResponseWriter
	Request       *http.Request
	OuterTurn     *hotPathOuterTurn
}

func (s *Server) runDirectTurn(ctx context.Context, turn *hotPathTurn, output normalizedStageOutput) error {
	directTerminal := hotPathTerminalDispositionSuccess
	reachedTerminal := false
	defer func() {
		// Emit the single direct-mode terminal observation exactly once. The
		// tool-turn path leaves reachedTerminal false so an agent round-trip is
		// not mistaken for a logical terminal. Disposition is normalized before
		// projection so raw error text never reaches logs or labels (SDD S15).
		if reachedTerminal {
			s.observeHotPathTerminal(ctx, hotPathModeDirect, directTerminal, turn.RequestID, turn.StageID, turn.Preset.ID)
		}
	}()
	for _, call := range output.ToolCalls {
		if len(reservedPathsFromToolCall(call)) > 0 {
			directTerminal = hotPathTerminalDispositionValidationError
			reachedTerminal = true
			s.terminalPresetRequest(turn.RequestID, turn.OwnerEdgeID)
			return s.writeDirectError(turn, http.StatusBadRequest, "invalid_request_error", "direct flow violation: reserved artifact path .iop/job/ emitted in direct turn")
		}
	}
	if strings.TrimSpace(output.ResponseID) == "" {
		directTerminal = hotPathTerminalDispositionProviderError
		reachedTerminal = true
		s.terminalPresetRequest(turn.RequestID, turn.OwnerEdgeID)
		return s.writeDirectError(turn, http.StatusBadGateway, "api_error", "direct response is missing provider execution identity")
	}
	visible := cloneNormalizedStageOutput(output)
	if turn.OuterTurn != nil {
		if !output.ProgressivelyReleased {
			if err := runHotPathCollectedStage(ctx, turn.OuterTurn, turn.StageID, output); err != nil {
				directTerminal = hotPathTerminalDispositionProviderError
				reachedTerminal = true
				s.terminalPresetRequest(turn.RequestID, turn.OwnerEdgeID)
				return s.writeDirectError(turn, http.StatusBadGateway, "api_error", fmt.Sprintf("direct outer turn failed: %v", err))
			}
		}
		visible = hotPathCompatibilityOutput(turn.OuterTurn, output, turn.Protocol)
	}

	if len(visible.ToolCalls) > 0 {
		expected := make([]logicalRequestExpectedTool, 0, len(visible.ToolCalls))
		for _, call := range visible.ToolCalls {
			providerID := strings.TrimSpace(call.ProviderCallID)
			if providerID == "" {
				providerID = call.ID
			}
			expected = append(expected, logicalRequestExpectedTool{PublicCallID: call.ID, ProviderCallID: providerID})
		}
		issuedHash, err := directIssuedCallHash(turn.Protocol, visible)
		if err != nil {
			directTerminal = hotPathTerminalDispositionProviderError
			reachedTerminal = true
			s.terminalPresetRequest(turn.RequestID, turn.OwnerEdgeID)
			return s.writeDirectError(turn, http.StatusBadGateway, "api_error", err.Error())
		}
		if turn.RequestID != "" {
			if _, err := s.requestCoordinator.awaitToolResults(turn.RequestID, turn.OwnerEdgeID, turn.StageID, expected, issuedHash); err != nil {
				directTerminal = hotPathTerminalDispositionValidationError
				reachedTerminal = true
				s.terminalPresetRequest(turn.RequestID, turn.OwnerEdgeID)
				return s.writeDirectError(turn, http.StatusBadRequest, "invalid_request_error", fmt.Sprintf("failed to await tool results: %v", err))
			}
		}
		if turn.OuterTurn != nil {
			turn.OuterTurn.commitTerminalSuccess(output.TerminalReason)
			visible = hotPathCompatibilityOutput(turn.OuterTurn, visible, turn.Protocol)
		}
		if err := s.writeDirectResponse(turn, visible); err != nil {
			// Classify the response-write failure through the closed error mapper
			// so a caller-canceled or timed-out endpoint write wins over
			// provider_error, matching the cleanup post-write ownership rule.
			directTerminal = hotPathTerminalDispositionFromKind(hotPathDispositionForError(err))
			reachedTerminal = true
			s.terminalPresetRequest(turn.RequestID, turn.OwnerEdgeID)
			return err
		}
		// Tool turn: the logical request is still waiting for agent tool
		// results, so this HTTP turn is not a logical terminal.
		return nil
	}

	if turn.OuterTurn != nil {
		turn.OuterTurn.commitTerminalSuccess(output.TerminalReason)
		visible = hotPathCompatibilityOutput(turn.OuterTurn, visible, turn.Protocol)
	}
	if err := s.writeDirectResponse(turn, visible); err != nil {
		// The final direct response also resolves cancellation/timeout through the
		// closed error mapper before the deferred exact-once terminal emission.
		directTerminal = hotPathTerminalDispositionFromKind(hotPathDispositionForError(err))
		reachedTerminal = true
		if turn.RequestID != "" {
			s.terminalPresetRequest(turn.RequestID, turn.OwnerEdgeID)
		}
		return err
	}
	if turn.RequestID != "" {
		s.terminalPresetRequest(turn.RequestID, turn.OwnerEdgeID)
	}
	if hotPathIsProviderLengthTerminal(output.TerminalReason) {
		directTerminal = hotPathTerminalDispositionLength
	}
	reachedTerminal = true
	return nil
}

func directIssuedCallHash(protocol string, output normalizedStageOutput) (string, error) {
	if protocol == "anthropic" {
		blocks, err := json.Marshal(map[string]any{"role": "assistant", "content": anthropicDirectBlocks(output)})
		if err != nil {
			return "", err
		}
		return anthropicIssuedCallHash(blocks)
	}
	message, err := json.Marshal(openAIDirectMessage(output))
	if err != nil {
		return "", err
	}
	return chatIssuedCallHash(message)
}

func (s *Server) writeDirectError(turn *hotPathTurn, status int, errorType, message string) error {
	disposition := hotPathTerminalDisposition{
		Kind: hotPathDispositionProviderError, Cause: message, Source: "direct_error",
	}
	if turn != nil && turn.OuterTurn != nil {
		turn.OuterTurn.commitTerminalError(errorType, errorType)
		if selected, ok := turn.OuterTurn.terminalDisposition(); ok {
			disposition = selected
		}
	} else if strings.Contains(strings.ToLower(errorType), "invalid") {
		disposition.Kind = hotPathDispositionValidationError
	}
	if turn.Protocol == "anthropic" {
		if !writeHotPathAnthropicOuterError(turn, status, errorType, message) {
			policy := anthropicHotPathPolicy(disposition)
			if !policy.silent {
				writeAnthropicError(turn.Writer, policy.status, policy.errorType, message)
			}
		}
	} else {
		if !writeHotPathChatOuterError(turn, status, errorType, message, disposition) {
			policy := chatHotPathPolicy(disposition)
			if !policy.silent {
				writeError(turn.Writer, policy.status, policy.errorType, message)
			}
		}
	}
	return fmt.Errorf("%s: %s", errorType, message)
}

func (s *Server) writeDirectResponse(turn *hotPathTurn, output normalizedStageOutput) error {
	if turn.Protocol == "anthropic" {
		return writeAnthropicDirectResponse(turn, output)
	}
	return writeOpenAIDirectResponse(turn, output)
}

func directPublicModel(turn *hotPathTurn) string {
	if model := strings.TrimSpace(turn.PublicModelID); model != "" {
		return model
	}
	if model := strings.TrimSpace(turn.Dispatch.ExternalModelID); model != "" {
		return model
	}
	return turn.Dispatch.Target
}

func openAIDirectMessage(output normalizedStageOutput) chatMessage {
	message := chatMessage{Role: "assistant", Content: output.Content, ReasoningContent: output.Reasoning}
	for _, call := range output.ToolCalls {
		message.ToolCalls = append(message.ToolCalls, openAIDirectToolCall(call))
	}
	return message
}

func openAIDirectToolCall(call normalizedToolCall) map[string]any {
	return map[string]any{
		"id": call.ID, "type": "function",
		"function": map[string]any{"name": call.Name, "arguments": directToolArguments(call)},
	}
}

func directToolArguments(call normalizedToolCall) string {
	if strings.TrimSpace(call.RawArgs) != "" {
		return call.RawArgs
	}
	raw, _ := json.Marshal(call.Arguments)
	return string(raw)
}

func writeOpenAIDirectResponse(turn *hotPathTurn, output normalizedStageOutput) error {
	if handled, err := writeHotPathChatOuterResponse(turn, output); handled {
		return err
	}
	model := directPublicModel(turn)
	finishReason := openAIDirectFinishReason(output.TerminalReason)
	if finishReason == "" {
		if len(output.ToolCalls) > 0 {
			finishReason = "tool_calls"
		} else {
			finishReason = "stop"
		}
	}
	if turn.Stream {
		return writeOpenAIDirectStream(turn, output, model, finishReason)
	}
	response := map[string]any{
		"id": output.ResponseID, "object": "chat.completion", "created": output.Created, "model": model,
		"choices": []any{map[string]any{
			"index": 0, "message": openAIDirectMessage(output), "finish_reason": finishReason,
		}},
	}
	if len(output.Usage) > 0 {
		response["usage"] = output.Usage
	}
	return writeDirectJSON(turn.Writer, http.StatusOK, response)
}

func openAIDirectFinishReason(reason string) string {
	switch strings.TrimSpace(reason) {
	case "end_turn":
		return "stop"
	case "tool_use":
		return "tool_calls"
	case "max_tokens":
		return "length"
	default:
		return strings.TrimSpace(reason)
	}
}

func writeOpenAIDirectStream(turn *hotPathTurn, output normalizedStageOutput, model, finishReason string) error {
	flusher, ok := turn.Writer.(http.Flusher)
	if !ok {
		return fmt.Errorf("response writer does not support flushing")
	}
	w := turn.Writer
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.WriteHeader(http.StatusOK)
	emit := func(delta map[string]any, reason string, usage json.RawMessage) error {
		choice := map[string]any{"index": 0, "delta": delta, "finish_reason": nil}
		if reason != "" {
			choice["finish_reason"] = reason
		}
		chunk := map[string]any{
			"id": output.ResponseID, "object": "chat.completion.chunk", "created": output.Created,
			"model": model, "choices": []any{choice},
		}
		if len(usage) > 0 {
			chunk["usage"] = usage
		}
		return writeDirectSSEData(w, flusher, chunk)
	}
	if err := emit(map[string]any{"role": "assistant"}, "", nil); err != nil {
		return err
	}
	if output.Reasoning != "" {
		if err := emit(map[string]any{"reasoning_content": output.Reasoning}, "", nil); err != nil {
			return err
		}
	}
	if output.Content != "" {
		if err := emit(map[string]any{"content": output.Content}, "", nil); err != nil {
			return err
		}
	}
	if len(output.ToolCalls) > 0 {
		calls := make([]any, 0, len(output.ToolCalls))
		for index, call := range output.ToolCalls {
			value := openAIDirectToolCall(call)
			value["index"] = index
			calls = append(calls, value)
		}
		if err := emit(map[string]any{"tool_calls": calls}, "", nil); err != nil {
			return err
		}
	}
	if err := emit(map[string]any{}, finishReason, output.Usage); err != nil {
		return err
	}
	if _, err := fmt.Fprint(w, "data: [DONE]\n\n"); err != nil {
		return err
	}
	flusher.Flush()
	return nil
}

func anthropicDirectBlocks(output normalizedStageOutput) []map[string]any {
	blocks := make([]map[string]any, 0, 2+len(output.ToolCalls))
	if output.Reasoning != "" {
		blocks = append(blocks, map[string]any{"type": "thinking", "thinking": output.Reasoning, "signature": output.ReasoningSignature})
	}
	if output.Content != "" {
		blocks = append(blocks, map[string]any{"type": "text", "text": output.Content})
	}
	for _, call := range output.ToolCalls {
		var input any
		if json.Unmarshal([]byte(directToolArguments(call)), &input) != nil {
			input = map[string]any{}
		}
		blocks = append(blocks, map[string]any{"type": "tool_use", "id": call.ID, "name": call.Name, "input": input})
	}
	return blocks
}

func writeAnthropicDirectResponse(turn *hotPathTurn, output normalizedStageOutput) error {
	if handled, err := writeHotPathAnthropicOuterResponse(turn, output); handled {
		return err
	}
	codec := newAnthropicHotPathCodec(
		turn.Writer, directPublicModel(turn), turn.Stream, turn.RequestID, 0,
	)
	codec.outer = turn.OuterTurn
	return codec.write(output)
}

func anthropicDirectStopReason(reason string) string {
	switch strings.TrimSpace(reason) {
	case "length":
		return "max_tokens"
	case "tool_calls":
		return "tool_use"
	case "stop":
		return "end_turn"
	default:
		return strings.TrimSpace(reason)
	}
}

func writeAnthropicDirectStream(turn *hotPathTurn, output normalizedStageOutput, model, stopReason string) error {
	flusher, ok := turn.Writer.(http.Flusher)
	if !ok {
		return fmt.Errorf("response writer does not support flushing")
	}
	w := turn.Writer
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache")
	w.WriteHeader(http.StatusOK)
	startUsage := anthropicStartUsage(output.Usage)
	message := map[string]any{
		"id": output.ResponseID, "type": "message", "role": "assistant", "model": model,
		"content": []any{}, "stop_reason": nil, "stop_sequence": nil,
	}
	if len(startUsage) > 0 {
		message["usage"] = startUsage
	}
	if err := writeDirectAnthropicEvent(w, flusher, "message_start", map[string]any{"type": "message_start", "message": message}); err != nil {
		return err
	}
	for index, block := range anthropicDirectBlocks(output) {
		blockType, _ := block["type"].(string)
		startBlock := make(map[string]any, len(block))
		for key, value := range block {
			startBlock[key] = value
		}
		switch blockType {
		case "text":
			startBlock["text"] = ""
		case "thinking":
			startBlock["thinking"] = ""
			startBlock["signature"] = ""
		case "tool_use":
			startBlock["input"] = map[string]any{}
		}
		if err := writeDirectAnthropicEvent(w, flusher, "content_block_start", map[string]any{
			"type": "content_block_start", "index": index, "content_block": startBlock,
		}); err != nil {
			return err
		}
		var delta map[string]any
		switch blockType {
		case "text":
			delta = map[string]any{"type": "text_delta", "text": block["text"]}
		case "thinking":
			delta = map[string]any{"type": "thinking_delta", "thinking": block["thinking"]}
		case "tool_use":
			raw, _ := json.Marshal(block["input"])
			delta = map[string]any{"type": "input_json_delta", "partial_json": string(raw)}
		}
		if err := writeDirectAnthropicEvent(w, flusher, "content_block_delta", map[string]any{
			"type": "content_block_delta", "index": index, "delta": delta,
		}); err != nil {
			return err
		}
		if blockType == "thinking" && block["signature"] != "" {
			if err := writeDirectAnthropicEvent(w, flusher, "content_block_delta", map[string]any{
				"type": "content_block_delta", "index": index,
				"delta": map[string]any{"type": "signature_delta", "signature": block["signature"]},
			}); err != nil {
				return err
			}
		}
		if err := writeDirectAnthropicEvent(w, flusher, "content_block_stop", map[string]any{
			"type": "content_block_stop", "index": index,
		}); err != nil {
			return err
		}
	}
	delta := map[string]any{
		"type": "message_delta", "delta": map[string]any{"stop_reason": stopReason, "stop_sequence": nil},
	}
	if len(output.Usage) > 0 {
		delta["usage"] = output.Usage
	}
	if err := writeDirectAnthropicEvent(w, flusher, "message_delta", delta); err != nil {
		return err
	}
	return writeDirectAnthropicEvent(w, flusher, "message_stop", map[string]any{"type": "message_stop"})
}

func anthropicStartUsage(raw json.RawMessage) json.RawMessage {
	if len(raw) == 0 {
		return nil
	}
	var usage map[string]any
	if json.Unmarshal(raw, &usage) != nil {
		return nil
	}
	for key := range usage {
		if key == "output_tokens" {
			delete(usage, key)
		}
	}
	encoded, _ := json.Marshal(usage)
	return encoded
}

func writeDirectJSON(w http.ResponseWriter, status int, value any) error {
	body, err := json.Marshal(value)
	if err != nil {
		return err
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_, err = w.Write(append(body, '\n'))
	return err
}

func writeDirectSSEData(w http.ResponseWriter, flusher http.Flusher, value any) error {
	body, err := json.Marshal(value)
	if err != nil {
		return err
	}
	if _, err := fmt.Fprintf(w, "data: %s\n\n", body); err != nil {
		return err
	}
	flusher.Flush()
	return nil
}

func writeDirectAnthropicEvent(w http.ResponseWriter, flusher http.Flusher, event string, value any) error {
	if err := writeAnthropicSSEEvent(w, event, value); err != nil {
		return err
	}
	flusher.Flush()
	return nil
}
