package prompts

import (
	"os"
	"path/filepath"
	"strings"
	"testing"
)

// ---------------------------------------------------------------------------
// InstructionsKey
// ---------------------------------------------------------------------------

// TestInstructionsKeyStable verifies that the same workspace list always
// produces the same key.
func TestInstructionsKeyStable(t *testing.T) {
	ws := []string{"/home/user/projectA", "/home/user/projectB"}
	k1 := InstructionsKey(ws)
	k2 := InstructionsKey(ws)
	if k1 != k2 {
		t.Fatalf("key should be stable: got %q then %q", k1, k2)
	}
}

// TestInstructionsKeyOrderIndependent verifies that workspace order does not
// affect the key, so wallfacer run ~/a ~/b and wallfacer run ~/b ~/a share
// the same instructions file.
func TestInstructionsKeyOrderIndependent(t *testing.T) {
	ws1 := []string{"/home/user/alpha", "/home/user/beta"}
	ws2 := []string{"/home/user/beta", "/home/user/alpha"}
	if InstructionsKey(ws1) != InstructionsKey(ws2) {
		t.Fatalf("key must be order-independent: %q != %q", InstructionsKey(ws1), InstructionsKey(ws2))
	}
}

// TestInstructionsKeyDifferentWorkspaces verifies that distinct workspace sets
// produce distinct keys.
func TestInstructionsKeyDifferentWorkspaces(t *testing.T) {
	k1 := InstructionsKey([]string{"/home/user/foo"})
	k2 := InstructionsKey([]string{"/home/user/bar"})
	if k1 == k2 {
		t.Fatalf("different workspaces should produce different keys, both got %q", k1)
	}
}

// TestInstructionsKeyLength verifies the key is exactly 16 hex characters.
func TestInstructionsKeyLength(t *testing.T) {
	k := InstructionsKey([]string{"/some/path"})
	if len(k) != 16 {
		t.Fatalf("expected 16-char key, got %d chars: %q", len(k), k)
	}
}

// ---------------------------------------------------------------------------
// BuildInstructionsContent
// ---------------------------------------------------------------------------

// TestBuildInstructionsContentDefault verifies that when no workspace
// AGENTS.md files exist the output contains the default template and
// workspace layout section but no per-repo instructions sections.
func TestBuildInstructionsContentDefault(t *testing.T) {
	dir := t.TempDir() // no AGENTS.md inside
	content := BuildInstructionsContent([]string{dir})
	if !strings.Contains(content, "# Workspace Instructions") {
		t.Fatal("content should contain the default template header")
	}
	if !strings.Contains(content, "## Workspace Layout") {
		t.Fatal("content should include workspace layout section")
	}
	name := filepath.Base(dir)
	if !strings.Contains(content, "/workspace/"+name+"/") {
		t.Fatalf("content should list workspace %q", name)
	}
	if strings.Contains(content, "## Repo-Specific Instructions") {
		t.Fatal("content should not include Repo-Specific Instructions section when no AGENTS.md exists")
	}
}

// TestBuildInstructionsContentWithWorkspaceCLAUDE verifies that when a single
// workspace has a AGENTS.md its path is referenced (not its content embedded),
// so the agent can read it directly from the workspace.
func TestBuildInstructionsContentWithWorkspaceCLAUDE(t *testing.T) {
	dir := t.TempDir()
	repoInstructions := "# My project rules\n\nDo the thing.\n"
	if err := os.WriteFile(filepath.Join(dir, "AGENTS.md"), []byte(repoInstructions), 0644); err != nil {
		t.Fatal(err)
	}

	content := BuildInstructionsContent([]string{dir})

	if !strings.Contains(content, "# Workspace Instructions") {
		t.Fatal("content should contain the default template header")
	}

	// Content must NOT be embedded; only a path reference should appear.
	if strings.Contains(content, repoInstructions) {
		t.Fatalf("single-workspace repo AGENTS.md content should not be embedded; got:\n%s", content)
	}

	name := filepath.Base(dir)
	pathRef := "/workspace/" + name + "/AGENTS.md"
	if !strings.Contains(content, pathRef) {
		t.Fatalf("single-workspace should contain path reference %q; got:\n%s", pathRef, content)
	}

	if !strings.Contains(content, "## Repo-Specific Instructions") {
		t.Fatal("content should include Repo-Specific Instructions section")
	}
}

// TestBuildInstructionsContentMissingCLAUDE verifies that a workspace without
// a AGENTS.md produces no Repo-Specific Instructions section.
func TestBuildInstructionsContentMissingCLAUDE(t *testing.T) {
	dir := t.TempDir() // no AGENTS.md
	content := BuildInstructionsContent([]string{dir})
	if !strings.Contains(content, "# Workspace Instructions") {
		t.Fatal("content should contain the default template header")
	}
	if strings.Contains(content, "## Repo-Specific Instructions") {
		t.Fatal("workspace without AGENTS.md should not produce Repo-Specific Instructions section")
	}
}

// TestBuildInstructionsContentSingleWorkspaceRef verifies that in single-repo
// mode a path reference appears and the content is not embedded.
func TestBuildInstructionsContentSingleWorkspaceRef(t *testing.T) {
	dir := t.TempDir()
	repoInstructions := "# Rules\nDo the thing.\n"
	if err := os.WriteFile(filepath.Join(dir, "AGENTS.md"), []byte(repoInstructions), 0644); err != nil {
		t.Fatal(err)
	}

	content := BuildInstructionsContent([]string{dir})

	name := filepath.Base(dir)
	// A path reference must appear.
	pathRef := "/workspace/" + name + "/AGENTS.md"
	if !strings.Contains(content, pathRef) {
		t.Fatalf("single-workspace output should contain path reference %q", pathRef)
	}
	// Content must NOT be embedded.
	if strings.Contains(content, repoInstructions) {
		t.Fatal("single-workspace content should not be embedded")
	}
}

// TestBuildInstructionsContentMultipleWorkspaces verifies that AGENTS.md paths
// from several workspaces are listed in order, while workspaces without a
// AGENTS.md are silently omitted from the reference list.
func TestBuildInstructionsContentMultipleWorkspaces(t *testing.T) {
	dirA := t.TempDir()
	dirB := t.TempDir()
	dirC := t.TempDir()

	if err := os.WriteFile(filepath.Join(dirA, "AGENTS.md"), []byte("instructions for A\n"), 0644); err != nil {
		t.Fatal(err)
	}
	// dirB intentionally has no AGENTS.md

	if err := os.WriteFile(filepath.Join(dirC, "AGENTS.md"), []byte("instructions for C\n"), 0644); err != nil {
		t.Fatal(err)
	}

	content := BuildInstructionsContent([]string{dirA, dirB, dirC})

	nameA := filepath.Base(dirA)
	nameB := filepath.Base(dirB)
	nameC := filepath.Base(dirC)

	refA := "/workspace/" + nameA + "/AGENTS.md"
	refC := "/workspace/" + nameC + "/AGENTS.md"
	refB := "/workspace/" + nameB + "/AGENTS.md"

	if !strings.Contains(content, refA) {
		t.Errorf("expected path reference for workspace A: %q", refA)
	}
	if !strings.Contains(content, refC) {
		t.Errorf("expected path reference for workspace C: %q", refC)
	}
	// dirB has no AGENTS.md — its path should not appear in repo refs.
	if strings.Contains(content, refB) {
		t.Errorf("workspace B (no AGENTS.md) should not appear in references")
	}

	// Embedded content must not be present.
	if strings.Contains(content, "instructions for A") || strings.Contains(content, "instructions for C") {
		t.Error("repo AGENTS.md content should not be embedded in output")
	}

	// A's reference must come before C's reference.
	posA := strings.Index(content, refA)
	posC := strings.Index(content, refC)
	if posA > posC {
		t.Error("workspace A reference should appear before workspace C reference")
	}
}

// TestBuildInstructionsContentTrailingNewline verifies that the generated
// content always ends with a newline regardless of workspace AGENTS.md state.
func TestBuildInstructionsContentTrailingNewline(t *testing.T) {
	dir := t.TempDir()
	// Repo AGENTS.md exists so a path reference section is emitted.
	if err := os.WriteFile(filepath.Join(dir, "AGENTS.md"), []byte("no newline at end"), 0644); err != nil {
		t.Fatal(err)
	}

	content := BuildInstructionsContent([]string{dir})

	if !strings.HasSuffix(content, "\n") {
		t.Fatal("content should end with a newline")
	}
}

// ---------------------------------------------------------------------------
// EnsureInstructions
// ---------------------------------------------------------------------------

// TestEnsureInstructionsCreatesFile verifies that the function
// creates a new instructions file when one does not exist yet.
func TestEnsureInstructionsCreatesFile(t *testing.T) {
	configDir := t.TempDir()
	ws := t.TempDir()

	path, err := EnsureInstructions(configDir, []string{ws})
	if err != nil {
		t.Fatal("EnsureInstructions:", err)
	}

	if _, err := os.Stat(path); err != nil {
		t.Fatalf("instructions file should exist at %q: %v", path, err)
	}

	data, _ := os.ReadFile(path)
	if !strings.Contains(string(data), "Workspace Instructions") {
		t.Fatalf("instructions file should contain default template, got:\n%s", data)
	}
}

// TestEnsureInstructionsIdempotent verifies that calling EnsureInstructions a
// second time does NOT overwrite manually edited content.
func TestEnsureInstructionsIdempotent(t *testing.T) {
	configDir := t.TempDir()
	ws := t.TempDir()

	path, err := EnsureInstructions(configDir, []string{ws})
	if err != nil {
		t.Fatal(err)
	}

	customContent := "# My custom instructions\n"
	if err := os.WriteFile(path, []byte(customContent), 0644); err != nil {
		t.Fatal(err)
	}

	// Calling again should not overwrite the custom content.
	path2, err := EnsureInstructions(configDir, []string{ws})
	if err != nil {
		t.Fatal(err)
	}
	if path != path2 {
		t.Fatalf("path changed between calls: %q vs %q", path, path2)
	}

	data, _ := os.ReadFile(path)
	if string(data) != customContent {
		t.Fatalf("existing content should be preserved; got:\n%s", data)
	}
}

// TestEnsureInstructionsIncludesWorkspaceCLAUDE verifies that a
// newly created instructions file references the single workspace's AGENTS.md path.
func TestEnsureInstructionsIncludesWorkspaceCLAUDE(t *testing.T) {
	configDir := t.TempDir()
	ws := t.TempDir()

	repoInstructions := "# Project-specific rules\n"
	if err := os.WriteFile(filepath.Join(ws, "AGENTS.md"), []byte(repoInstructions), 0644); err != nil {
		t.Fatal(err)
	}

	path, err := EnsureInstructions(configDir, []string{ws})
	if err != nil {
		t.Fatal(err)
	}

	data, _ := os.ReadFile(path)
	name := filepath.Base(ws)
	pathRef := "/workspace/" + name + "/AGENTS.md"
	// A path reference must appear; content must not be embedded.
	if !strings.Contains(string(data), pathRef) {
		t.Fatalf("instructions file should reference single-workspace AGENTS.md path; got:\n%s", data)
	}
	if strings.Contains(string(data), repoInstructions) {
		t.Fatalf("instructions file should not embed single-workspace AGENTS.md content; got:\n%s", data)
	}
}

// ---------------------------------------------------------------------------
// ReinitInstructions
// ---------------------------------------------------------------------------

// TestReinitInstructionsOverwrites verifies that ReinitInstructions replaces any
// previously written (or manually edited) content.
func TestReinitInstructionsOverwrites(t *testing.T) {
	configDir := t.TempDir()
	ws := t.TempDir()

	// First write stale content.
	path, err := EnsureInstructions(configDir, []string{ws})
	if err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(path, []byte("stale content"), 0644); err != nil {
		t.Fatal(err)
	}

	// Now add a AGENTS.md to the workspace and reinit.
	repoInstructions := "# Fresh instructions\n"
	if err := os.WriteFile(filepath.Join(ws, "AGENTS.md"), []byte(repoInstructions), 0644); err != nil {
		t.Fatal(err)
	}

	path2, err := ReinitInstructions(configDir, []string{ws})
	if err != nil {
		t.Fatal(err)
	}
	if path != path2 {
		t.Fatalf("path should be stable: %q vs %q", path, path2)
	}

	data, _ := os.ReadFile(path)
	if strings.Contains(string(data), "stale content") {
		t.Fatal("ReinitInstructions should have overwritten stale content")
	}
	// For a single workspace a path reference must appear; content must not be embedded.
	name := filepath.Base(ws)
	pathRef := "/workspace/" + name + "/AGENTS.md"
	if !strings.Contains(string(data), pathRef) {
		t.Fatalf("ReinitInstructions should reference single-workspace AGENTS.md path; got:\n%s", data)
	}
	if strings.Contains(string(data), repoInstructions) {
		t.Fatalf("ReinitInstructions should not embed single-workspace AGENTS.md content; got:\n%s", data)
	}
}

// ---------------------------------------------------------------------------
// Instructions template rendering
// ---------------------------------------------------------------------------

// TestInstructionsTemplateRender verifies that the Manager.Instructions method
// renders the template with the provided data.
func TestInstructionsTemplateRender(t *testing.T) {
	content := Default.Instructions(InstructionsData{
		Workspaces: []InstructionsWorkspace{
			{Name: "repo-a"},
			{Name: "repo-b"},
		},
		RepoInstructionRefs: []InstructionsRepoRef{
			{Workspace: "repo-a", Filename: "AGENTS.md"},
		},
	})

	if !strings.Contains(content, "# Workspace Instructions") {
		t.Fatal("rendered content should contain template header")
	}
	if !strings.Contains(content, "/workspace/repo-a/") {
		t.Fatal("rendered content should list repo-a workspace")
	}
	if !strings.Contains(content, "/workspace/repo-b/") {
		t.Fatal("rendered content should list repo-b workspace")
	}
	if !strings.Contains(content, "/workspace/repo-a/AGENTS.md") {
		t.Fatal("rendered content should reference repo-a AGENTS.md")
	}
	if !strings.Contains(content, "## Repo-Specific Instructions") {
		t.Fatal("rendered content should include Repo-Specific Instructions section")
	}
}

// TestInstructionsTemplateNoRefs verifies that the Repo-Specific Instructions
// section is omitted when no repos have instruction files.
func TestInstructionsTemplateNoRefs(t *testing.T) {
	content := Default.Instructions(InstructionsData{
		Workspaces: []InstructionsWorkspace{{Name: "my-repo"}},
	})

	if !strings.Contains(content, "/workspace/my-repo/") {
		t.Fatal("should list workspace")
	}
	if strings.Contains(content, "## Repo-Specific Instructions") {
		t.Fatal("should not include Repo-Specific Instructions when no refs")
	}
}
