//go:build e2e

// Package workspace contains e2e tests for workspace file operations
// (list, read, scan, delete, reset) in the AgentTeam channel.
package workspace

import (
	"encoding/json"
	"os"
	"path/filepath"
	"testing"

	"github.com/tidbcloud/AgentTeam/desktop/e2e/helpers"
	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
)

func setupWorkspaceTest(t *testing.T) (*helpers.FakeServer, *helpers.StubDispatcher) {
	t.Helper()
	home := t.TempDir()
	t.Setenv("HOME", home)
	t.Setenv(profile.ConfigDirEnvKey, filepath.Join(home, ".agentteam"))
	fs := helpers.NewFakeServer()
	ch, disp := helpers.StartChannel(fs)
	fs.DrainReady()
	t.Cleanup(func() {
		helpers.StopChannel(ch)
		fs.Close()
	})
	return fs, disp
}

func TestWorkspaceReadNonexistent(t *testing.T) {
	fs, _ := setupWorkspaceTest(t)

	fs.Send(map[string]any{
		"type": "agent:workspace:read", "agentId": "no-such-agent",
		"path": "nonexistent.txt", "requestId": "req-missing",
	})

	resp := fs.DrainUntil("agent:workspace:file_content")
	if helpers.DecodeField(resp, "requestId") != "req-missing" {
		t.Errorf("requestId=%q, want req-missing", helpers.DecodeField(resp, "requestId"))
	}
	if helpers.DecodeBoolField(resp, "binary") {
		t.Error("expected binary=false for missing file response")
	}

	var payload map[string]json.RawMessage
	if err := json.Unmarshal(resp, &payload); err != nil {
		t.Fatalf("unmarshal response: %v", err)
	}
	if raw, ok := payload["content"]; !ok || string(raw) != "null" {
		t.Errorf("expected content=null, got %s", string(raw))
	}
}

func TestWorkspaceReset(t *testing.T) {
	fs, _ := setupWorkspaceTest(t)
	home := os.Getenv("HOME")

	agentID := "reset-test-agent"
	agentDir := filepath.Join(home, ".agentteam", "agents", agentID)
	os.MkdirAll(agentDir, 0755)
	os.WriteFile(filepath.Join(agentDir, "data.txt"), []byte("data"), 0644)
	os.WriteFile(filepath.Join(agentDir, "MEMORY.md"), []byte("# Memory"), 0644)

	fs.Send(map[string]any{"type": "agent:reset-workspace", "agentId": agentID})
	resp := fs.DrainUntil("agent:status")
	if helpers.DecodeField(resp, "agentId") != agentID {
		t.Errorf("agentId=%q, want %q", helpers.DecodeField(resp, "agentId"), agentID)
	}
	if helpers.DecodeField(resp, "status") != "inactive" {
		t.Errorf("status=%q, want inactive", helpers.DecodeField(resp, "status"))
	}

	if _, err := os.Stat(agentDir); !os.IsNotExist(err) {
		t.Fatalf("expected workspace directory to be removed after reset, stat err=%v", err)
	}

	fs.Send(map[string]string{"type": "machine:workspace:scan"})
	scan := fs.DrainUntil("machine:workspace:scan_result")

	var result struct {
		Directories []struct {
			DirectoryName string `json:"directoryName"`
		} `json:"directories"`
	}
	if err := json.Unmarshal(scan, &result); err != nil {
		t.Fatalf("unmarshal scan result: %v", err)
	}
	for _, directory := range result.Directories {
		if directory.DirectoryName == agentID {
			t.Fatalf("expected reset workspace %q to be absent from scan result", agentID)
		}
	}
}

func TestMachineScanMultipleAgents(t *testing.T) {
	fs, _ := setupWorkspaceTest(t)
	home := os.Getenv("HOME")

	for _, name := range []string{"agent-a", "agent-b", "agent-c"} {
		dir := filepath.Join(home, ".agentteam", "agents", name)
		os.MkdirAll(dir, 0755)
		os.WriteFile(filepath.Join(dir, "file.txt"), []byte(name), 0644)
	}

	fs.Send(map[string]string{"type": "machine:workspace:scan"})
	scan := fs.DrainUntil("machine:workspace:scan_result")

	var result struct {
		Directories []struct {
			DirectoryName string `json:"directoryName"`
		} `json:"directories"`
	}
	json.Unmarshal(scan, &result)

	found := map[string]bool{}
	for _, d := range result.Directories {
		found[d.DirectoryName] = true
	}
	for _, name := range []string{"agent-a", "agent-b", "agent-c"} {
		if !found[name] {
			t.Errorf("missing agent %q in scan result", name)
		}
	}
}

func TestWorkspaceListHiddenFilesExcluded(t *testing.T) {
	fs, _ := setupWorkspaceTest(t)
	home := os.Getenv("HOME")

	agentID := "hidden-files-agent"
	agentDir := filepath.Join(home, ".agentteam", "agents", agentID)
	os.MkdirAll(agentDir, 0755)
	os.WriteFile(filepath.Join(agentDir, ".hidden"), []byte("hidden"), 0644)
	os.WriteFile(filepath.Join(agentDir, "visible.txt"), []byte("visible"), 0644)
	os.MkdirAll(filepath.Join(agentDir, "node_modules"), 0755)

	fs.Send(map[string]any{"type": "agent:workspace:list", "agentId": agentID, "dirPath": ""})
	tree := fs.DrainUntil("agent:workspace:file_tree")

	var treeMsg struct {
		Files []struct {
			Name string `json:"name"`
		} `json:"files"`
	}
	json.Unmarshal(tree, &treeMsg)
	for _, f := range treeMsg.Files {
		if f.Name == ".hidden" {
			t.Error("hidden files should be excluded from listing")
		}
		if f.Name == "node_modules" {
			t.Error("node_modules should be excluded from listing")
		}
	}
}

func TestMachineDeleteInvalidNames(t *testing.T) {
	fs, _ := setupWorkspaceTest(t)

	for _, tt := range []struct {
		name string
		dir  string
	}{
		{"path separator", "foo/bar"},
		{"parent traversal", ".."},
		{"backslash", "foo\\bar"},
	} {
		t.Run(tt.name, func(t *testing.T) {
			fs.Send(map[string]any{"type": "machine:workspace:delete", "directoryName": tt.dir})
			resp := fs.DrainUntil("machine:workspace:delete_result")
			if helpers.DecodeBoolField(resp, "success") {
				t.Errorf("expected failure for directory name %q", tt.dir)
			}
		})
	}
}

func TestWorkspaceReadBinaryDetection(t *testing.T) {
	fs, _ := setupWorkspaceTest(t)
	home := os.Getenv("HOME")

	agentID := "binary-agent"
	agentDir := filepath.Join(home, ".agentteam", "agents", agentID)
	os.MkdirAll(agentDir, 0755)

	for _, ext := range []string{".png", ".jpg", ".gif", ".zip", ".tar", ".pdf", ".exe"} {
		filename := "test" + ext
		os.WriteFile(filepath.Join(agentDir, filename), []byte{0x89, 0x50, 0x4E, 0x47}, 0644)

		fs.Send(map[string]any{
			"type": "agent:workspace:read", "agentId": agentID,
			"path": filename, "requestId": "req-" + ext,
		})
		resp := fs.DrainUntil("agent:workspace:file_content")
		if !helpers.DecodeBoolField(resp, "binary") {
			t.Errorf("expected binary=true for %s", ext)
		}
	}
}

func TestWorkspaceApplySeedFiles(t *testing.T) {
	fs, _ := setupWorkspaceTest(t)
	home := os.Getenv("HOME")

	agentID := "seed-files-agent"
	agentDir := filepath.Join(home, ".agentteam", "agents", agentID)

	fs.Send(map[string]any{
		"type":      "agent:workspace:apply_seed_files",
		"agentId":   agentID,
		"requestId": "seed-req-1",
		"seedFiles": []types.WorkspaceSeedFile{
			{Path: "memory/system/identity.md", Content: "# Seeded Role\n"},
			{Path: ".agents/skills/reviewer/SKILL.md", Content: "name: reviewer\n"},
		},
	})

	resp := fs.DrainUntil("agent:workspace:apply_seed_files_result")
	if helpers.DecodeField(resp, "requestId") != "seed-req-1" {
		t.Fatalf("requestId=%q, want seed-req-1", helpers.DecodeField(resp, "requestId"))
	}
	if !helpers.DecodeBoolField(resp, "success") {
		t.Fatalf("expected apply seed files success, got %s", string(resp))
	}

	roleBytes, err := os.ReadFile(filepath.Join(agentDir, "memory", "system", "identity.md"))
	if err != nil {
		t.Fatalf("read memory/system/identity.md: %v", err)
	}
	if string(roleBytes) != "# Seeded Role\n" {
		t.Fatalf("memory/system/identity.md=%q, want seeded content", string(roleBytes))
	}

	skillBytes, err := os.ReadFile(filepath.Join(agentDir, ".agents", "skills", "reviewer", "SKILL.md"))
	if err != nil {
		t.Fatalf("read SKILL.md: %v", err)
	}
	if string(skillBytes) != "name: reviewer\n" {
		t.Fatalf("SKILL.md=%q, want reviewer content", string(skillBytes))
	}
}

func TestWorkspaceDeletePath(t *testing.T) {
	fs, _ := setupWorkspaceTest(t)
	home := os.Getenv("HOME")

	agentID := "delete-path-agent"
	agentDir := filepath.Join(home, ".agentteam", "agents", agentID)
	targetDir := filepath.Join(agentDir, ".agentteam", "tmp", "delete-me")
	if err := os.MkdirAll(filepath.Join(targetDir, "skills"), 0755); err != nil {
		t.Fatalf("mkdir target dir: %v", err)
	}
	if err := os.MkdirAll(filepath.Join(targetDir, "memory", "system"), 0755); err != nil {
		t.Fatalf("mkdir identity dir: %v", err)
	}
	if err := os.WriteFile(filepath.Join(targetDir, "memory", "system", "identity.md"), []byte("# Draft\n"), 0644); err != nil {
		t.Fatalf("write draft role: %v", err)
	}

	fs.Send(map[string]any{
		"type":      "agent:workspace:delete",
		"agentId":   agentID,
		"path":      ".agentteam/tmp/delete-me",
		"requestId": "delete-req-1",
	})

	resp := fs.DrainUntil("agent:workspace:delete_result")
	if helpers.DecodeField(resp, "requestId") != "delete-req-1" {
		t.Fatalf("requestId=%q, want delete-req-1", helpers.DecodeField(resp, "requestId"))
	}
	if !helpers.DecodeBoolField(resp, "success") {
		t.Fatalf("expected workspace delete success, got %s", string(resp))
	}

	if _, err := os.Stat(targetDir); !os.IsNotExist(err) {
		t.Fatalf("expected %s to be removed, stat err=%v", targetDir, err)
	}
}
