package dashboard

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/desktop/internal/agent"
	"github.com/tidbcloud/AgentTeam/desktop/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
)

func setupAgentWorkspaceRouter(t *testing.T) *gin.Engine {
	t.Helper()

	dir := t.TempDir()
	t.Setenv(profile.ConfigDirEnvKey, dir)

	require.NoError(t, profile.Default().Agents().Add(agent.Config{
		ID:      "agent-workspace-test",
		Runtime: "codex",
	}))

	workDir := profile.AgentWorkDir("agent-workspace-test")
	require.NoError(t, os.MkdirAll(filepath.Join(workDir, "state"), 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(workDir, "MEMORY.md"), []byte("# Memory\n"), 0o644))
	require.NoError(t, os.WriteFile(filepath.Join(workDir, "state", "active-context.md"), []byte("active\n"), 0o644))

	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.GET("/agents/:id/workspace/tree", NewAgentWorkspaceTreeHandler())
	router.GET("/agents/:id/workspace/file", NewAgentWorkspaceFileHandler())
	return router
}

func TestAgentWorkspaceTreeAndFileHandlers(t *testing.T) {
	router := setupAgentWorkspaceRouter(t)

	treeReq := httptest.NewRequest(http.MethodGet, "/agents/agent-workspace-test/workspace/tree", nil)
	treeRec := httptest.NewRecorder()
	router.ServeHTTP(treeRec, treeReq)

	require.Equal(t, http.StatusOK, treeRec.Code, treeRec.Body.String())
	var tree dto.AgentWorkspaceTreeResponse
	require.NoError(t, json.Unmarshal(treeRec.Body.Bytes(), &tree))
	require.Equal(t, "~/.agentteam/agents/agent-workspace-test", tree.Root)
	require.NotEmpty(t, tree.Nodes)

	fileReq := httptest.NewRequest(http.MethodGet, "/agents/agent-workspace-test/workspace/file?path=state%2Factive-context.md", nil)
	fileRec := httptest.NewRecorder()
	router.ServeHTTP(fileRec, fileReq)

	require.Equal(t, http.StatusOK, fileRec.Code, fileRec.Body.String())
	var file dto.AgentWorkspaceFileResponse
	require.NoError(t, json.Unmarshal(fileRec.Body.Bytes(), &file))
	require.Equal(t, "state/active-context.md", file.Path)
	require.Equal(t, "active\n", file.Content)
	require.False(t, file.Binary)
}

func TestAgentWorkspaceTreeListsOneDirectoryLevel(t *testing.T) {
	router := setupAgentWorkspaceRouter(t)
	workDir := profile.AgentWorkDir("agent-workspace-test")

	projectRoot := filepath.Join(workDir, ".agentteam", "taskclinic")
	frontendDir := filepath.Join(projectRoot, "frontend")
	require.NoError(t, os.MkdirAll(frontendDir, 0o755))
	require.NoError(t, os.MkdirAll(filepath.Join(projectRoot, "mobile"), 0o755))
	require.NoError(t, os.MkdirAll(filepath.Join(projectRoot, "server"), 0o755))
	for i := 0; i < 3; i++ {
		require.NoError(t, os.WriteFile(filepath.Join(frontendDir, fmt.Sprintf("file-%04d.txt", i)), []byte("x"), 0o644))
	}

	req := httptest.NewRequest(http.MethodGet, "/agents/agent-workspace-test/workspace/tree", nil)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	require.Equal(t, http.StatusOK, rec.Code, rec.Body.String())
	var tree dto.AgentWorkspaceTreeResponse
	require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tree))
	require.False(t, tree.Truncated)

	agentTeamDir := findAgentWorkspaceNode(tree.Nodes, ".agentteam")
	require.NotNil(t, agentTeamDir)
	require.Empty(t, agentTeamDir.Children, "root listing should not recursively expand child directories")

	req = httptest.NewRequest(http.MethodGet, "/agents/agent-workspace-test/workspace/tree?dirPath=.agentteam", nil)
	rec = httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	require.Equal(t, http.StatusOK, rec.Code, rec.Body.String())
	require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tree))
	require.NotNil(t, findAgentWorkspaceNode(tree.Nodes, "taskclinic"))

	req = httptest.NewRequest(http.MethodGet, "/agents/agent-workspace-test/workspace/tree?dirPath=.agentteam%2Ftaskclinic", nil)
	rec = httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	require.Equal(t, http.StatusOK, rec.Code, rec.Body.String())
	require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tree))
	require.NotNil(t, findAgentWorkspaceNode(tree.Nodes, "frontend"))
	require.NotNil(t, findAgentWorkspaceNode(tree.Nodes, "mobile"))
	require.NotNil(t, findAgentWorkspaceNode(tree.Nodes, "server"))
	require.Empty(t, findAgentWorkspaceNode(tree.Nodes, "frontend").Children)

	req = httptest.NewRequest(http.MethodGet, "/agents/agent-workspace-test/workspace/tree?dirPath=.agentteam%2Ftaskclinic%2Ffrontend", nil)
	rec = httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	require.Equal(t, http.StatusOK, rec.Code, rec.Body.String())
	require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &tree))
	require.NotNil(t, findAgentWorkspaceNode(tree.Nodes, "file-0000.txt"))
}

func TestAgentWorkspaceTreeRejectsTraversal(t *testing.T) {
	router := setupAgentWorkspaceRouter(t)

	req := httptest.NewRequest(http.MethodGet, "/agents/agent-workspace-test/workspace/tree?dirPath=..%2Fother", nil)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	require.Equal(t, http.StatusBadRequest, rec.Code, rec.Body.String())
}

func TestAgentWorkspaceFileRejectsTraversal(t *testing.T) {
	router := setupAgentWorkspaceRouter(t)

	req := httptest.NewRequest(http.MethodGet, "/agents/agent-workspace-test/workspace/file?path=..%2Fagents.yaml", nil)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	require.Equal(t, http.StatusBadRequest, rec.Code, rec.Body.String())
}

func findAgentWorkspaceNode(nodes []dto.AgentWorkspaceNode, name string) *dto.AgentWorkspaceNode {
	for index := range nodes {
		if nodes[index].Name == name {
			return &nodes[index]
		}
	}
	return nil
}
