package agent

import (
	"context"
	"encoding/json"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/desktop/internal/consts"
)

func TestCodexReadThreadAcceptsLegacyAndTaggedFileChangeKinds(t *testing.T) {
	driver, writer := newTestCodexDriver(t, func(req capturedRPCRequest) appServerEnvelope {
		require.Equal(t, "thread/read", req.Method)
		require.Equal(t, "thread-1", req.Params["threadId"])
		require.Equal(t, true, req.Params["includeTurns"])
		return appServerEnvelope{Result: json.RawMessage(`{
			"thread": {
				"id": "thread-1",
				"status": {"type": "idle"},
				"turns": [{
					"id": "turn-1",
					"status": {"type": "completed"},
					"items": [{
						"type": "fileChange",
						"id": "file-change-1",
						"changes": [
							{"path": "tagged-added.go", "kind": {"type": "add"}, "diff": "+package added"},
							{"path": "tagged-deleted.go", "kind": {"type": "delete"}, "diff": "-package deleted"},
							{"path": "tagged-updated.go", "kind": {"type": "update", "move_path": "tagged-renamed.go"}, "diff": "@@ -1 +1 @@"},
							{"path": "legacy-added.go", "kind": "add", "diff": "+package legacy_added"},
							{"path": "legacy-deleted.go", "kind": "delete", "diff": "-package legacy_deleted"},
							{"path": "legacy-updated.go", "kind": "update", "diff": "@@ -1 +1 @@"}
						]
					}]
				}]
			}
		}`)}
	})

	result, err := driver.readThread(context.Background(), "thread-1", true)

	require.NoError(t, err)
	require.Len(t, writer.requests, 1)
	require.Len(t, result.Thread.Turns, 1)
	require.Len(t, result.Thread.Turns[0].Items, 1)
	require.Equal(t, appServerChangeKind("add"), result.Thread.Turns[0].Items[0].Changes[0].Kind)
	require.Equal(t, appServerChangeKind("delete"), result.Thread.Turns[0].Items[0].Changes[1].Kind)
	require.Equal(t, appServerChangeKind("update"), result.Thread.Turns[0].Items[0].Changes[2].Kind)
	require.Equal(t, appServerChangeKind("add"), result.Thread.Turns[0].Items[0].Changes[3].Kind)
	require.Equal(t, appServerChangeKind("delete"), result.Thread.Turns[0].Items[0].Changes[4].Kind)
	require.Equal(t, appServerChangeKind("update"), result.Thread.Turns[0].Items[0].Changes[5].Kind)
}

func TestCodexFileChangeEventAcceptsTaggedKindAndPreservesLegacyToolInput(t *testing.T) {
	driver, _ := newTestCodexDriver(t, nil)
	driver.translatedLines = make(chan string, 1)
	bindCodexPrimaryActiveThreadForIsolationTest(driver)

	notification := json.RawMessage(`{
		"threadId":"thread-primary",
		"turnId":"turn-primary",
		"item":{
			"id":"file-change-1",
			"type":"fileChange",
			"changes":[{
				"path":"original.go",
				"kind":{"type":"update","move_path":"renamed.go"},
				"diff":"@@ -1 +1 @@"
			}]
		}
	}`)
	driver.handleItemNotification(notification, false)

	events := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, events, 1)
	require.Equal(t, consts.ParsedEventKindToolCall, events[0].Kind)
	require.Equal(t, "file_change", events[0].ToolName)
	require.JSONEq(t, `{"path":"original.go","kind":"update"}`, events[0].ToolInput.(string))

	driver.handleItemNotification(notification, true)
	require.Empty(t, driver.translatedLines)
}

func TestCodexReconcileOldThreadDoesNotOverwriteCurrentThreadModel(t *testing.T) {
	driver, writer := newTestCodexDriver(t, func(req capturedRPCRequest) appServerEnvelope {
		require.Equal(t, "thread/read", req.Method)
		require.Equal(t, "thread-old", req.Params["threadId"])
		require.Equal(t, true, req.Params["includeTurns"])
		return appServerEnvelope{Result: rawRPCResult(t, appServerThreadResult{
			Thread: appServerThread{
				ID:     "thread-old",
				Status: appServerThreadStatus{Type: "idle"},
			},
			Model: "model-old",
		})}
	})
	driver.threadID = "thread-current"
	driver.threadStatus = appServerThreadStatus{Type: "idle"}
	driver.effectiveModel = "model-current"

	got, err := driver.ReconcileRuntimeInput(context.Background(), "input-old", "thread-old")

	require.NoError(t, err)
	require.Equal(t, RuntimeInputReconciliationAbsent, got)
	require.Len(t, writer.requests, 1)
	require.Equal(t, "model-old", driver.threadModel("thread-old"))
	require.Equal(t, "model-current", driver.threadModel("thread-current"))
	require.Equal(t, "model-current", driver.effectiveModel)
	require.Equal(t, "thread-current", driver.threadID)
	require.Equal(t, "idle", normalizeThreadStatus(driver.threadStatus).Type)

	usage := appServerThreadTokenUsage{
		Last: appServerTokenUsageBreakdown{
			InputTokens: 1,
			TotalTokens: 1,
		},
		Total: appServerTokenUsageBreakdown{
			InputTokens: 1,
			TotalTokens: 1,
		},
	}
	model, emit, _, _ := driver.recordThreadTokenUsage("thread-current", "turn-current", usage)
	require.True(t, emit)
	require.Equal(t, "model-current", model)
}

func TestCodexCompactThreadUsesCurrentProtocolShape(t *testing.T) {
	driver, writer := newTestCodexDriver(t, func(req capturedRPCRequest) appServerEnvelope {
		require.Equal(t, "thread/compact/start", req.Method)
		require.Equal(t, map[string]any{"threadId": "thread-1"}, req.Params)
		return appServerEnvelope{Result: rawRPCResult(t, struct{}{})}
	})
	driver.smallModel = "model-small-is-not-a-supported-compact-override"

	err := driver.compactThread(context.Background(), "thread-1")

	require.NoError(t, err)
	require.Len(t, writer.requests, 1)
}
