package schema

import (
	"encoding/json"
	"testing"

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

func ptrToolKind(k ToolKind) *ToolKind                   { return &k }
func ptrToolCallStatus(s ToolCallStatus) *ToolCallStatus { return &s }

func TestAgentCapabilitiesJSON(t *testing.T) {
	caps := AgentCapabilities{
		PromptCapabilities:  &PromptCapabilities{},
		McpCapabilities:     &McpCapabilities{},
		SessionCapabilities: &SessionCapabilities{},
	}
	data, err := json.Marshal(caps)
	require.NoError(t, err)

	var decoded AgentCapabilities
	require.NoError(t, json.Unmarshal(data, &decoded))
	assert.NotNil(t, decoded.PromptCapabilities)
}

func TestClientCapabilitiesJSON(t *testing.T) {
	caps := ClientCapabilities{
		Fs: &FileSystemCapabilities{},
	}
	data, err := json.Marshal(caps)
	require.NoError(t, err)

	var decoded ClientCapabilities
	require.NoError(t, json.Unmarshal(data, &decoded))
	assert.NotNil(t, decoded.Fs)
}

func TestSessionUpdateAgentMessageRoundtrip(t *testing.T) {
	update := SessionUpdate{
		SessionUpdate:     SessionUpdateKindAgentMessageChunk,
		AgentMessageChunk: &ContentChunk{Content: &ContentBlock{Type: TypeContentBlockKindText, Text: &TextContent{Text: "hello world"}}},
	}
	data, err := json.Marshal(update)
	require.NoError(t, err)
	assert.Contains(t, string(data), `"sessionUpdate":"agent_message_chunk"`)

	var decoded SessionUpdate
	require.NoError(t, json.Unmarshal(data, &decoded))
	assert.Equal(t, SessionUpdateKindAgentMessageChunk, decoded.SessionUpdate)
	require.NotNil(t, decoded.AgentMessageChunk)
	assert.Equal(t, TypeContentBlockKindText, decoded.AgentMessageChunk.Content.Type)
	require.NotNil(t, decoded.AgentMessageChunk.Content.Text)
	assert.Equal(t, "hello world", decoded.AgentMessageChunk.Content.Text.Text)
}

func TestSessionUpdateThoughtRoundtrip(t *testing.T) {
	update := SessionUpdate{
		SessionUpdate:     SessionUpdateKindAgentThoughtChunk,
		AgentThoughtChunk: &ContentChunk{Content: &ContentBlock{Type: TypeContentBlockKindText, Text: &TextContent{Text: "thinking..."}}},
	}
	data, err := json.Marshal(update)
	require.NoError(t, err)
	assert.Contains(t, string(data), `"sessionUpdate":"agent_thought_chunk"`)

	var decoded SessionUpdate
	require.NoError(t, json.Unmarshal(data, &decoded))
	assert.Equal(t, SessionUpdateKindAgentThoughtChunk, decoded.SessionUpdate)
	require.NotNil(t, decoded.AgentThoughtChunk)
	require.NotNil(t, decoded.AgentThoughtChunk.Content.Text)
	assert.Equal(t, "thinking...", decoded.AgentThoughtChunk.Content.Text.Text)
}

func TestSessionUpdateToolCallRoundtrip(t *testing.T) {
	tcid := ToolCallId("tc-1")
	tc := ToolCall{
		ToolCallId: &tcid,
		Title:      "Read file",
		Kind:       ptrToolKind(ToolKindRead),
		Status:     ptrToolCallStatus(ToolCallStatusInProgress),
	}
	update := SessionUpdate{
		SessionUpdate: SessionUpdateKindToolCall,
		ToolCall:      &tc,
	}
	data, err := json.Marshal(update)
	require.NoError(t, err)
	assert.Contains(t, string(data), `"sessionUpdate":"tool_call"`)

	var decoded SessionUpdate
	require.NoError(t, json.Unmarshal(data, &decoded))
	assert.Equal(t, SessionUpdateKindToolCall, decoded.SessionUpdate)
	require.NotNil(t, decoded.ToolCall)
	require.NotNil(t, decoded.ToolCall.ToolCallId)
	assert.Equal(t, "tc-1", string(*decoded.ToolCall.ToolCallId))
}

func TestSessionUpdatePlanRoundtrip(t *testing.T) {
	high := PlanEntryPriorityHigh
	medium := PlanEntryPriorityMedium
	pending := PlanEntryStatusPending
	completed := PlanEntryStatusCompleted
	entries := []*PlanEntry{
		{Content: "Step 1", Priority: &high, Status: &pending},
		{Content: "Step 2", Priority: &medium, Status: &completed},
	}
	update := SessionUpdate{
		SessionUpdate: SessionUpdateKindPlan,
		Plan:          &Plan{Entries: entries},
	}
	data, err := json.Marshal(update)
	require.NoError(t, err)
	assert.Contains(t, string(data), `"sessionUpdate":"plan"`)

	var decoded SessionUpdate
	require.NoError(t, json.Unmarshal(data, &decoded))
	assert.Equal(t, SessionUpdateKindPlan, decoded.SessionUpdate)
	require.NotNil(t, decoded.Plan)
	require.Len(t, decoded.Plan.Entries, 2)
	assert.Equal(t, "Step 1", decoded.Plan.Entries[0].Content)
	require.NotNil(t, decoded.Plan.Entries[0].Priority)
	assert.Equal(t, PlanEntryPriorityHigh, *decoded.Plan.Entries[0].Priority)
	require.NotNil(t, decoded.Plan.Entries[1].Status)
	assert.Equal(t, PlanEntryStatusCompleted, *decoded.Plan.Entries[1].Status)
}

func TestSessionUpdateMissingField(t *testing.T) {
	var update SessionUpdate
	err := json.Unmarshal([]byte(`{"content":{"type":"text","text":"hi"}}`), &update)
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "missing sessionUpdate")
}

func TestContentChunkRoundtrip(t *testing.T) {
	chunk := ContentChunk{
		Content: &ContentBlock{Type: "text", Text: &TextContent{Text: "hello"}},
	}
	data, err := json.Marshal(chunk)
	require.NoError(t, err)

	var decoded ContentChunk
	require.NoError(t, json.Unmarshal(data, &decoded))
	require.NotNil(t, decoded.Content)
	require.NotNil(t, decoded.Content.Text)
	assert.Equal(t, "hello", decoded.Content.Text.Text)
}

func TestPlanEntryJSON(t *testing.T) {
	high := PlanEntryPriorityHigh
	inProgress := PlanEntryStatusInProgress
	entry := PlanEntry{
		Content:  "Do something",
		Priority: &high,
		Status:   &inProgress,
	}
	data, err := json.Marshal(entry)
	require.NoError(t, err)

	var decoded PlanEntry
	require.NoError(t, json.Unmarshal(data, &decoded))
	assert.Equal(t, "Do something", decoded.Content)
	require.NotNil(t, decoded.Priority)
	assert.Equal(t, PlanEntryPriorityHigh, *decoded.Priority)
	require.NotNil(t, decoded.Status)
	assert.Equal(t, PlanEntryStatusInProgress, *decoded.Status)
}

func TestSessionModeJSON(t *testing.T) {
	modeId := SessionModeId("code")
	mode := SessionMode{
		Id:          &modeId,
		Name:        "Code",
		Description: ptrString("Code mode"),
	}
	data, err := json.Marshal(mode)
	require.NoError(t, err)

	var decoded SessionMode
	require.NoError(t, json.Unmarshal(data, &decoded))
	require.NotNil(t, decoded.Id)
	assert.Equal(t, "code", string(*decoded.Id))
	assert.Equal(t, "Code", decoded.Name)
}

func TestSessionConfigSelectRoundtrip(t *testing.T) {
	valId := SessionConfigValueId("gpt-4")
	sel := SessionConfigSelect{
		CurrentValue: &valId,
	}
	data, err := json.Marshal(sel)
	require.NoError(t, err)

	var decoded SessionConfigSelect
	require.NoError(t, json.Unmarshal(data, &decoded))
	require.NotNil(t, decoded.CurrentValue)
	assert.Equal(t, "gpt-4", string(*decoded.CurrentValue))
}
