package openai

import (
	"encoding/json"
	"strings"
	"testing"

	"nex/backend/internal/conversion/canonical"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestStreamEncoder_MessageStart(t *testing.T) {
	e := NewStreamEncoder()
	event := canonical.NewMessageStartEvent("chatcmpl-1", "gpt-4")

	chunks := e.EncodeEvent(event)
	require.Len(t, chunks, 1)

	s := string(chunks[0])
	assert.True(t, strings.HasPrefix(s, "data: "))
	assert.Contains(t, s, "chatcmpl-1")
	assert.Contains(t, s, "chat.completion.chunk")

	var payload map[string]any
	data := strings.TrimPrefix(s, "data: ")
	data = strings.TrimRight(data, "\n")
	require.NoError(t, json.Unmarshal([]byte(data), &payload))
	choices, okch := payload["choices"].([]any)
	require.True(t, okch)
	msgMap, okmm := choices[0].(map[string]any)
	require.True(t, okmm)
	delta, okd := msgMap["delta"].(map[string]any)
	require.True(t, okd)
	assert.Equal(t, "assistant", delta["role"])
}

func TestStreamEncoder_TextDelta(t *testing.T) {
	e := NewStreamEncoder()
	event := canonical.NewContentBlockDeltaEvent(0, canonical.StreamDelta{Type: "text_delta", Text: "你好"})

	chunks := e.EncodeEvent(event)
	require.Len(t, chunks, 1)

	s := string(chunks[0])
	assert.Contains(t, s, "你好")
}

func TestStreamEncoder_MessageStop(t *testing.T) {
	e := NewStreamEncoder()
	event := canonical.NewMessageStopEvent()

	chunks := e.EncodeEvent(event)
	require.Len(t, chunks, 1)
	assert.Equal(t, "data: [DONE]\n\n", string(chunks[0]))
}

func TestStreamEncoder_Buffering(t *testing.T) {
	e := NewStreamEncoder()

	// ContentBlockStart 应被缓冲，不输出
	startEvent := canonical.NewContentBlockStartEvent(0, canonical.StreamContentBlock{Type: "text", Text: ""})
	chunks := e.EncodeEvent(startEvent)
	assert.Nil(t, chunks)
	assert.NotNil(t, e.bufferedStart)

	// 第一个 delta 触发输出（清空缓冲）
	deltaEvent := canonical.NewContentBlockDeltaEvent(0, canonical.StreamDelta{Type: "text_delta", Text: "hello"})
	chunks = e.EncodeEvent(deltaEvent)
	require.NotEmpty(t, chunks)
	assert.Nil(t, e.bufferedStart)
}

func TestStreamEncoder_ContentBlockStop_ReturnsNil(t *testing.T) {
	e := NewStreamEncoder()
	idx := 0
	event := canonical.CanonicalStreamEvent{
		Type:  canonical.EventContentBlockStop,
		Index: &idx,
	}
	chunks := e.EncodeEvent(event)
	assert.Nil(t, chunks)
}

func TestStreamEncoder_Ping_ReturnsNil(t *testing.T) {
	e := NewStreamEncoder()
	event := canonical.NewPingEvent()
	chunks := e.EncodeEvent(event)
	assert.Nil(t, chunks)
}

func TestStreamEncoder_Error_ReturnsNil(t *testing.T) {
	e := NewStreamEncoder()
	event := canonical.NewErrorEvent("test_error", "测试错误")
	chunks := e.EncodeEvent(event)
	assert.Nil(t, chunks)
}

func TestStreamEncoder_Flush_ReturnsNil(t *testing.T) {
	e := NewStreamEncoder()
	chunks := e.Flush()
	assert.Nil(t, chunks)
}

func TestStreamEncoder_ThinkingDelta(t *testing.T) {
	e := NewStreamEncoder()
	event := canonical.NewContentBlockDeltaEvent(0, canonical.StreamDelta{
		Type:     string(canonical.DeltaTypeThinking),
		Thinking: "思考内容",
	})

	chunks := e.EncodeEvent(event)
	require.Len(t, chunks, 1)

	s := string(chunks[0])
	assert.Contains(t, s, "reasoning_content")
	assert.Contains(t, s, "思考内容")
}

func TestStreamEncoder_InputJSONDelta(t *testing.T) {
	e := NewStreamEncoder()

	e.EncodeEvent(canonical.NewContentBlockStartEvent(0, canonical.StreamContentBlock{
		Type: "tool_use",
		ID:   "call_1",
		Name: "get_weather",
	}))

	event := canonical.NewContentBlockDeltaEvent(0, canonical.StreamDelta{
		Type:        string(canonical.DeltaTypeInputJSON),
		PartialJSON: "{\"city\":\"北京\"}",
	})

	chunks := e.EncodeEvent(event)
	require.NotEmpty(t, chunks)

	s := string(chunks[0])
	assert.Contains(t, s, "tool_calls")
	assert.Contains(t, s, "北京")
}

func TestStreamEncoder_MessageDelta_WithStopReason(t *testing.T) {
	e := NewStreamEncoder()
	sr := canonical.StopReasonEndTurn
	event := canonical.CanonicalStreamEvent{
		Type:       canonical.EventMessageDelta,
		StopReason: &sr,
	}

	chunks := e.EncodeEvent(event)
	require.NotEmpty(t, chunks)

	s := string(chunks[0])
	assert.Contains(t, s, "finish_reason")
	assert.Contains(t, s, "stop")
}

func TestStreamEncoder_MessageDelta_WithUsage(t *testing.T) {
	e := NewStreamEncoder()
	usage := canonical.CanonicalUsage{
		InputTokens:  100,
		OutputTokens: 50,
	}
	event := canonical.CanonicalStreamEvent{
		Type:  canonical.EventMessageDelta,
		Usage: &usage,
	}

	chunks := e.EncodeEvent(event)
	require.NotEmpty(t, chunks)

	s := string(chunks[0])
	assert.Contains(t, s, "usage")
	assert.Contains(t, s, "prompt_tokens")
}

func TestStreamEncoder_InputJSONDelta_SubsequentDelta(t *testing.T) {
	e := NewStreamEncoder()

	e.EncodeEvent(canonical.NewContentBlockStartEvent(0, canonical.StreamContentBlock{
		Type: "tool_use",
		ID:   "call_1",
		Name: "get_weather",
	}))

	e.EncodeEvent(canonical.NewContentBlockDeltaEvent(0, canonical.StreamDelta{
		Type:        string(canonical.DeltaTypeInputJSON),
		PartialJSON: "{\"city\":",
	}))

	event := canonical.NewContentBlockDeltaEvent(0, canonical.StreamDelta{
		Type:        string(canonical.DeltaTypeInputJSON),
		PartialJSON: "\"Beijing\"}",
	})

	chunks := e.EncodeEvent(event)
	require.NotEmpty(t, chunks)

	s := string(chunks[0])
	assert.Contains(t, s, "tool_calls")
	assert.Contains(t, s, "Beijing")
}

func TestStreamEncoder_MessageStart_NilMessage(t *testing.T) {
	e := NewStreamEncoder()
	event := canonical.CanonicalStreamEvent{Type: canonical.EventMessageStart}

	chunks := e.EncodeEvent(event)
	require.Len(t, chunks, 1)

	s := string(chunks[0])
	assert.Contains(t, s, "chat.completion.chunk")
}

func TestStreamEncoder_UnknownEvent_ReturnsNil(t *testing.T) {
	e := NewStreamEncoder()
	event := canonical.CanonicalStreamEvent{Type: "unknown_type"}
	chunks := e.EncodeEvent(event)
	assert.Nil(t, chunks)
}

func TestStreamEncoder_ContentBlockDelta_NilDelta(t *testing.T) {
	e := NewStreamEncoder()
	event := canonical.CanonicalStreamEvent{Type: canonical.EventContentBlockDelta}
	chunks := e.EncodeEvent(event)
	assert.Nil(t, chunks)
}

func TestStreamEncoder_MultiToolCall_IndexMapping(t *testing.T) {
	e := NewStreamEncoder()

	e.EncodeEvent(canonical.NewContentBlockStartEvent(0, canonical.StreamContentBlock{
		Type: "tool_use",
		ID:   "call_1",
		Name: "get_weather",
	}))

	firstDelta := canonical.NewContentBlockDeltaEvent(0, canonical.StreamDelta{
		Type:        string(canonical.DeltaTypeInputJSON),
		PartialJSON: `{"city":"北京"}`,
	})
	chunks := e.EncodeEvent(firstDelta)
	require.NotEmpty(t, chunks)
	s := string(chunks[0])
	assert.Contains(t, s, `"index":0`)
	assert.Contains(t, s, "get_weather")
	assert.Contains(t, s, "北京")

	e.EncodeEvent(canonical.NewContentBlockStopEvent(0))

	e.EncodeEvent(canonical.NewContentBlockStartEvent(1, canonical.StreamContentBlock{
		Type: "tool_use",
		ID:   "call_2",
		Name: "get_time",
	}))

	secondDelta := canonical.NewContentBlockDeltaEvent(1, canonical.StreamDelta{
		Type:        string(canonical.DeltaTypeInputJSON),
		PartialJSON: `{"tz":"Asia/Shanghai"}`,
	})
	chunks = e.EncodeEvent(secondDelta)
	require.NotEmpty(t, chunks)
	s = string(chunks[0])
	assert.Contains(t, s, `"index":1`)
	assert.Contains(t, s, "get_time")
	assert.Contains(t, s, "Asia/Shanghai")

	subsequentDelta0 := canonical.NewContentBlockDeltaEvent(0, canonical.StreamDelta{
		Type:        string(canonical.DeltaTypeInputJSON),
		PartialJSON: `"more_data"`,
	})
	chunks = e.EncodeEvent(subsequentDelta0)
	require.NotEmpty(t, chunks)
	s = string(chunks[0])
	assert.Contains(t, s, `"index":0`)
	assert.NotContains(t, s, "get_weather")
	assert.Contains(t, s, "more_data")

	subsequentDelta1 := canonical.NewContentBlockDeltaEvent(1, canonical.StreamDelta{
		Type:        string(canonical.DeltaTypeInputJSON),
		PartialJSON: `"more_time"`,
	})
	chunks = e.EncodeEvent(subsequentDelta1)
	require.NotEmpty(t, chunks)
	s = string(chunks[0])
	assert.Contains(t, s, `"index":1`)
	assert.Contains(t, s, "more_time")
}
