package handlers

import (
	"time"

	jsoniter "github.com/json-iterator/go"
	"github.com/yaoapp/kun/log"
	"github.com/yaoapp/yao/agent/context"
	"github.com/yaoapp/yao/agent/i18n"
	"github.com/yaoapp/yao/agent/output"
	"github.com/yaoapp/yao/agent/output/message"
)

// DefaultStreamHandler creates a default stream handler that sends messages via context
// This handler is used when no custom handler is provided
func DefaultStreamHandler(ctx *context.Context) message.StreamFunc {

	// Create stream state manager
	state := &streamState{
		ctx:            ctx,
		inGroup:        false,
		currentGroupID: "",
		messageSeq:     0,
	}

	return func(chunkType message.StreamChunkType, data []byte) int {
		trace, _ := ctx.Trace()
		if trace != nil {
			trace.Info(i18n.T(ctx.Locale, "llm.handlers.stream.info"), map[string]any{"data": string(data)})
		}

		// Handle different chunk types
		switch chunkType {
		case message.ChunkStreamStart:
			return state.handleStreamStart(data)

		case message.ChunkMessageStart:
			return state.handleMessageStart(data)

		case message.ChunkText:
			return state.handleText(data)

		case message.ChunkThinking:
			return state.handleThinking(data)

		case message.ChunkToolCall:
			return state.handleToolCall(data)

		case message.ChunkExecute:
			return state.handleExecute(data)

		case message.ChunkMetadata:
			return state.handleMetadata(data)

		case message.ChunkError:
			return state.handleError(data)

		case message.ChunkMessageEnd:
			return state.handleMessageEnd(data)

		case message.ChunkStreamEnd:
			return state.handleStreamEnd(data)

		default:
			// Unknown chunk type, continue
			return 0
		}
	}
}

// streamState manages the state of the streaming process
type streamState struct {
	ctx            *context.Context
	inGroup        bool
	currentGroupID string // Current group ID (shared by all chunks in the group)
	currentType    string // Track the current message type (text, thinking, tool_call)
	buffer         []byte
	chunkCount     int                    // Track number of chunks in current group
	messageSeq     int                    // Message sequence number (for generating readable IDs)
	groupStartTime time.Time              // Track when group started
	lastExecStatus string                 // Last observed execute status in current group ("running", "completed", "error")
	lastExecProps  map[string]interface{} // Accumulated execute props for the current group (merged across chunks)
}

// handleStreamStart handles stream start event
func (s *streamState) handleStreamStart(data []byte) int {
	// Send event message to indicate stream has started
	// This is a lifecycle event, CUI clients can show it, OpenAI clients will ignore it
	var startData message.EventStreamStartData
	err := jsoniter.Unmarshal(data, &startData)
	if err != nil {
		log.Error("Failed to unmarshal stream start data: %v", err)
	}
	msg := output.NewEventMessage("stream_start", "Stream started", startData)
	s.ctx.Send(msg)
	return 0
}

// handleMessageStart handles message start event
func (s *streamState) handleMessageStart(data []byte) int {
	// Parse message start data first to get the message ID
	var startData message.EventMessageStartData
	if err := jsoniter.Unmarshal(data, &startData); err != nil {
		log.Error("Failed to unmarshal message start data: %v", err)
		return 0
	}

	// Use the message ID from the start data, or generate one if not provided
	messageID := startData.MessageID
	if messageID == "" {
		messageID = s.ctx.IDGenerator.GenerateMessageID()
		startData.MessageID = messageID
	}

	// Auto-set ThreadID from Stack for nested agent calls
	if startData.ThreadID == "" && s.ctx.Stack != nil && !s.ctx.Stack.IsRoot() {
		startData.ThreadID = s.ctx.Stack.ID
	}

	s.inGroup = true
	s.currentGroupID = messageID
	s.buffer = []byte{}
	s.chunkCount = 0
	s.messageSeq = 0 // Reset message sequence for each message
	s.groupStartTime = time.Now()

	// Send message_start event
	msg := output.NewEventMessage(message.EventMessageStart, "Message started", startData)
	s.ctx.Send(msg)

	return 0 // Continue
}

// handleText handles text content chunks
func (s *streamState) handleText(data []byte) int {
	if len(data) == 0 {
		return 0
	}

	// Track current message type
	s.currentType = message.TypeText

	// Append to buffer
	s.buffer = append(s.buffer, data...)
	s.chunkCount++
	s.messageSeq++

	// Send delta message
	// - ChunkID: Unique chunk ID (C1, C2, C3...) for this fragment
	// - MessageID: Same for all chunks of this logical message (frontend merges by message_id)
	msg := &message.Message{
		ChunkID:   s.ctx.IDGenerator.GenerateChunkID(), // Unique chunk ID
		MessageID: s.currentGroupID,                    // Message ID for merging (all chunks share this)
		Type:      message.TypeText,
		Delta:     true,
		Props: map[string]interface{}{
			"content": string(data),
		},
	}

	if err := s.ctx.Send(msg); err != nil {
		// Log error but continue streaming
		return 0
	}

	return 0 // Continue
}

// handleThinking handles thinking/reasoning chunks
func (s *streamState) handleThinking(data []byte) int {
	if len(data) == 0 {
		return 0
	}

	// Track current message type
	s.currentType = message.TypeThinking

	// Append to buffer
	s.buffer = append(s.buffer, data...)
	s.chunkCount++
	s.messageSeq++

	// Send delta message
	// - ChunkID: Unique chunk ID (C1, C2, C3...) for this fragment
	// - MessageID: Same for all chunks of this logical message (frontend merges by message_id)
	msg := &message.Message{
		ChunkID:   s.ctx.IDGenerator.GenerateChunkID(), // Unique chunk ID
		MessageID: s.currentGroupID,                    // Message ID for merging (all chunks share this)
		Type:      message.TypeThinking,
		Delta:     true,
		Props: map[string]interface{}{
			"content": string(data),
		},
	}

	if err := s.ctx.Send(msg); err != nil {
		return 0
	}

	return 0 // Continue
}

// handleToolCall handles tool call chunks
func (s *streamState) handleToolCall(data []byte) int {
	if len(data) == 0 {
		return 0
	}

	// Track current message type
	s.currentType = message.TypeToolCall

	// Append to buffer for message_end event
	s.buffer = append(s.buffer, data...)
	s.chunkCount++
	s.messageSeq++

	// Parse the tool call delta data (JSON array from OpenAI)
	var toolCallArray []map[string]interface{}
	if err := jsoniter.Unmarshal(data, &toolCallArray); err != nil {
		// If parse fails, log and skip this chunk
		return 0
	}

	// Extract tool call fields from delta
	// OpenAI delta typically has one element, but we handle arrays safely
	var props map[string]interface{}
	var deltaAction string
	var deltaPath string

	if len(toolCallArray) == 1 {
		tc := toolCallArray[0]
		props = map[string]interface{}{}

		hasIdentity := false
		if id, ok := tc["id"].(string); ok {
			props["id"] = id
			hasIdentity = true
		}
		if typ, ok := tc["type"].(string); ok {
			props["type"] = typ
			hasIdentity = true
		}
		if index, ok := tc["index"].(float64); ok {
			props["index"] = int(index)
		}
		if fn, ok := tc["function"].(map[string]interface{}); ok {
			if name, ok := fn["name"].(string); ok {
				props["name"] = name
				hasIdentity = true
			}
			if args, ok := fn["arguments"].(string); ok {
				props["arguments"] = args
			}
		}

		if hasIdentity {
			// First chunk with id/name/type: merge so all fields are applied.
			deltaAction = "merge"
		} else if _, ok := props["arguments"]; ok {
			// Subsequent chunk with only arguments fragment: append to arguments.
			deltaAction = "append"
			deltaPath = "arguments"
		} else {
			deltaAction = "merge"
		}
	} else {
		// Multiple tool calls in delta (rare) - keep as array
		props = map[string]interface{}{
			"calls": toolCallArray,
		}
		deltaAction = "merge"
	}

	// Send delta message
	// - ChunkID: Unique chunk ID (C1, C2, C3...) for this fragment
	// - MessageID: Same for all chunks of this logical message (frontend merges by message_id)
	// - DeltaAction: "append" for arguments chunks, "merge" for id/type/name chunks
	// - DeltaPath: "arguments" when appending arguments field
	//   OpenAI sends: first chunk has id/type/name, subsequent chunks only have arguments fragments
	msg := &message.Message{
		ChunkID:     s.ctx.IDGenerator.GenerateChunkID(), // Unique chunk ID
		MessageID:   s.currentGroupID,                    // Message ID for merging (all chunks share this)
		Type:        message.TypeToolCall,
		Delta:       true,
		DeltaAction: deltaAction, // "append" for arguments, "merge" for static fields
		DeltaPath:   deltaPath,   // "arguments" when appending
		Props:       props,       // Flattened tool call fields
	}

	if err := s.ctx.Send(msg); err != nil {
		return 0
	}

	return 0 // Continue
}

// handleExecute handles execute observation chunks from sandbox CLI agents.
// These represent tool actions observed inside the agent runtime (e.g., Bash, Read, Write).
func (s *streamState) handleExecute(data []byte) int {
	if len(data) == 0 {
		return 0
	}

	s.currentType = message.TypeExecute
	s.buffer = append(s.buffer, data...)
	s.chunkCount++
	s.messageSeq++

	var props map[string]interface{}
	if err := jsoniter.Unmarshal(data, &props); err != nil {
		return 0
	}

	if st, ok := props["status"].(string); ok {
		s.lastExecStatus = st
	}

	if s.lastExecProps == nil {
		s.lastExecProps = make(map[string]interface{})
	}
	for k, v := range props {
		s.lastExecProps[k] = v
	}

	deltaAction := "merge"

	msg := &message.Message{
		ChunkID:     s.ctx.IDGenerator.GenerateChunkID(),
		MessageID:   s.currentGroupID,
		Type:        message.TypeExecute,
		Delta:       true,
		DeltaAction: deltaAction,
		Props:       props,
	}

	if err := s.ctx.Send(msg); err != nil {
		return 0
	}

	return 0
}

// handleMetadata handles metadata chunks (usage, finish_reason, result_summary, etc.)
// For sandbox CLI agents, this carries token usage and result summaries.
func (s *streamState) handleMetadata(data []byte) int {
	if len(data) == 0 {
		return 0
	}

	var meta map[string]interface{}
	if err := jsoniter.Unmarshal(data, &meta); err != nil {
		return 0
	}

	if usage, ok := meta["usage"]; ok {
		msg := output.NewEventMessage("token/usage", "", usage)
		s.ctx.Send(msg)
	}

	if summary, ok := meta["result_summary"]; ok {
		msg := output.NewEventMessage("result/summary", "", summary)
		s.ctx.Send(msg)
	}

	return 0
}

// handleError handles error chunks
func (s *streamState) handleError(data []byte) int {
	// Send error message
	msg := output.NewErrorMessage(string(data), "stream_error")
	s.ctx.Send(msg)

	return 1 // Stop streaming on error
}

// handleMessageEnd handles message end event
func (s *streamState) handleMessageEnd(data []byte) int {
	if !s.inGroup {
		return 0
	}

	durationMs := time.Since(s.groupStartTime).Milliseconds()

	// Use the tracked message type (thinking, text, tool_call, etc.)
	msgType := s.currentType
	if msgType == "" {
		msgType = message.TypeText // Fallback to text if type not set
	}

	// Get ThreadID from Stack for nested agent calls
	var threadID string
	if s.ctx.Stack != nil && !s.ctx.Stack.IsRoot() {
		threadID = s.ctx.Stack.ID
	}

	// Get BlockID from metadata if available
	var blockID string
	if s.ctx != nil {
		if metadata := s.ctx.GetMessageMetadata(s.currentGroupID); metadata != nil {
			blockID = metadata.BlockID
		}
	}

	// Buffer the complete LLM message for storage
	// Delta chunks are not stored, but we need to save the final complete content
	// Skip if History is disabled in options
	shouldSkipHistory := s.ctx.Stack != nil && s.ctx.Stack.Options != nil &&
		s.ctx.Stack.Options.Skip != nil && s.ctx.Stack.Options.Skip.History

	// Execute messages have two (or more) phases sharing the same message_id:
	//   1. running / suspended / resumed — streamed for UI display only, NOT persisted
	//   2. completed / error — the final state, persisted to the buffer
	// Only persist when we have an explicit terminal status.
	isExecuteFinal := msgType == message.TypeExecute &&
		(s.lastExecStatus == "completed" || s.lastExecStatus == "error")
	skipExecute := msgType == message.TypeExecute && !isExecuteFinal

	if s.ctx.Buffer != nil && len(s.buffer) > 0 && !shouldSkipHistory && !skipExecute {
		assistantID := ""
		if s.ctx.Stack != nil {
			assistantID = s.ctx.Stack.AssistantID
		}

		var props map[string]interface{}
		switch msgType {
		case message.TypeToolCall:
			var toolCallData interface{}
			if err := jsoniter.Unmarshal(s.buffer, &toolCallData); err == nil {
				props = map[string]interface{}{
					"calls": toolCallData,
				}
			} else {
				props = map[string]interface{}{
					"content": string(s.buffer),
				}
			}
		case message.TypeExecute:
			if s.lastExecProps != nil {
				props = make(map[string]interface{}, len(s.lastExecProps))
				for k, v := range s.lastExecProps {
					props[k] = v
				}
			} else {
				props = map[string]interface{}{
					"content": string(s.buffer),
				}
			}
		default:
			props = map[string]interface{}{
				"content": string(s.buffer),
			}
		}

		s.ctx.Buffer.AddAssistantMessage(
			s.currentGroupID,
			msgType,
			props,
			blockID,
			threadID,
			assistantID,
			nil,
		)
	}

	// Build EventMessageEndData with complete content
	endData := message.EventMessageEndData{
		MessageID:  s.currentGroupID, // Use the message ID
		Type:       msgType,
		Timestamp:  time.Now().UnixMilli(),
		ThreadID:   threadID, // Include ThreadID for concurrent stream identification
		DurationMs: durationMs,
		ChunkCount: s.chunkCount,
		Status:     "completed",
		Extra: map[string]interface{}{
			"content": string(s.buffer), // Include complete content in the event
		},
	}

	// Send message_end event
	msg := output.NewEventMessage(message.EventMessageEnd, "Message completed", endData)
	s.ctx.Send(msg)

	// Reset state
	s.inGroup = false
	s.currentGroupID = ""
	s.currentType = ""
	s.buffer = []byte{}
	s.chunkCount = 0
	s.lastExecStatus = ""
	s.lastExecProps = nil

	return 0 // Continue
}

// handleStreamEnd handles stream end event
func (s *streamState) handleStreamEnd(data []byte) int {
	// Parse the stream end data
	var endData message.EventStreamEndData
	if err := jsoniter.Unmarshal(data, &endData); err != nil {
		log.Error("Failed to parse stream_end data: %v", err)
		s.ctx.Flush()
		return 0
	}

	// Send stream_end event as a message to frontend
	msg := output.NewEventMessage("stream_end", "Stream completed", endData)
	s.ctx.Send(msg)

	// Flush any remaining data
	s.ctx.Flush()
	return 0 // Continue (stream will end naturally)
}
