package project

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

	"cyberstrike-ai/internal/config"
	"cyberstrike-ai/internal/database"

	"go.uber.org/zap"
)

func sampleFactIndexWithFacts(projectLabel, summary string) string {
	return wrapFactIndexBlock("## 项目黑板索引（project: " + projectLabel + ", id: x）\n" +
		"- [target/a] target — " + summary + " (tentative)\n" +
		factIndexFooterGetDetail + "\n" +
		factIndexFooterWriteHint)
}

func TestReplaceFactIndexSection(t *testing.T) {
	t.Parallel()
	oldIndex := sampleFactIndexWithFacts("p1", "old summary")
	newIndex := sampleFactIndexWithFacts("p1", "new summary")

	t.Run("replaces index before next section", func(t *testing.T) {
		content := "你是助手\n\n" + oldIndex + "\n\n## 图片分析\n看截图"
		out, ok := ReplaceFactIndexSection(content, newIndex)
		if !ok {
			t.Fatal("expected replacement")
		}
		if strings.Contains(out, "old summary") {
			t.Fatalf("old index should be gone: %q", out)
		}
		if !strings.Contains(out, "new summary") || !strings.Contains(out, "## 图片分析") {
			t.Fatalf("expected new index and preserved vision section: %q", out)
		}
		if strings.Count(out, FactIndexSectionStartMarker) != 1 || strings.Count(out, FactIndexSectionEndMarker) != 1 {
			t.Fatalf("expected exactly one start/end marker pair: %q", out)
		}
	})

	t.Run("replaces index at end", func(t *testing.T) {
		content := "## 项目测试范围\nscope\n\n" + oldIndex
		out, ok := ReplaceFactIndexSection(content, newIndex)
		if !ok {
			t.Fatal("expected replacement")
		}
		if !strings.Contains(out, "## 项目测试范围") || !strings.Contains(out, "new summary") {
			t.Fatalf("scope preserved, index updated: %q", out)
		}
	})

	t.Run("summary with false markdown header does not truncate early", func(t *testing.T) {
		summaryWithFakeHeader := "see\n\n## fake header in summary"
		old := sampleFactIndexWithFacts("p1", summaryWithFakeHeader)
		newIdx := sampleFactIndexWithFacts("p1", "new summary")
		content := old + "\n\n## 图片分析\nvision"
		out, ok := ReplaceFactIndexSection(content, newIdx)
		if !ok {
			t.Fatal("expected replacement")
		}
		if strings.Contains(out, "fake header in summary") {
			t.Fatalf("old index tail should be fully removed: %q", out)
		}
	})

	t.Run("summary containing end marker text does not truncate early", func(t *testing.T) {
		summary := "note " + FactIndexSectionEndMarker + " in summary"
		old := sampleFactIndexWithFacts("p1", summary)
		newIdx := sampleFactIndexWithFacts("p1", "clean")
		content := old + "\n\n## 图片分析\nvision"
		out, ok := ReplaceFactIndexSection(content, newIdx)
		if !ok {
			t.Fatal("expected replacement")
		}
		if strings.Contains(out, "in summary") {
			t.Fatalf("old block should be fully removed: %q", out)
		}
	})

	t.Run("missing html markers does not replace", func(t *testing.T) {
		legacy := "## 项目黑板索引（project: p1, id: x）\n- [a] note — old (tentative)\n"
		newIdx := sampleFactIndexWithFacts("p1", "new")
		out, ok := ReplaceFactIndexSection("prefix\n\n"+legacy, newIdx)
		if ok {
			t.Fatalf("expected no replacement without markers: %q", out)
		}
	})

	t.Run("empty facts block", func(t *testing.T) {
		oldEmpty := wrapFactIndexBlock("## 项目黑板索引（project: p1, id: x）\n（暂无事实）\n" + factIndexFooterEmpty)
		newEmpty := sampleFactIndexWithFacts("p1", "first fact")
		out, ok := ReplaceFactIndexSection(oldEmpty, newEmpty)
		if !ok {
			t.Fatal("expected replacement")
		}
		if strings.Contains(out, "（暂无事实）") {
			t.Fatalf("old empty block should be gone: %q", out)
		}
	})

	t.Run("no marker", func(t *testing.T) {
		_, ok := ReplaceFactIndexSection("no blackboard here", newIndex)
		if ok {
			t.Fatal("expected false when marker missing")
		}
	})

	t.Run("empty fresh index", func(t *testing.T) {
		_, ok := ReplaceFactIndexSection(oldIndex, "  ")
		if ok {
			t.Fatal("expected false for empty fresh index")
		}
	})
}

func TestFactIndexSectionBounds_useHTMLMarkers(t *testing.T) {
	t.Parallel()
	body := sampleFactIndexWithFacts("p", "line with\n\n## not a real section") + "TAIL_SHOULD_DROP"
	start, ok := factIndexSectionStart(body)
	if !ok || !strings.HasPrefix(body[start:], FactIndexSectionStartMarker) {
		t.Fatalf("start should be at html start marker, got %d", start)
	}
	end, ok := factIndexSectionEnd(body, start)
	if !ok || body[end:] != "\nTAIL_SHOULD_DROP" {
		t.Fatalf("end should be after end marker, got remainder %q", body[end:])
	}
}

func TestBuildFactIndexBlock_includesHTMLMarkers(t *testing.T) {
	t.Parallel()
	dbPath := filepath.Join(t.TempDir(), "facts.db")
	db, err := database.NewDB(dbPath, zap.NewNop())
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()

	proj, err := db.CreateProject(&database.Project{Name: "marker-proj"})
	if err != nil {
		t.Fatal(err)
	}
	block, err := BuildFactIndexBlock(db, proj.ID, config.ProjectConfig{Enabled: true})
	if err != nil {
		t.Fatal(err)
	}
	if !strings.HasPrefix(strings.TrimSpace(block), FactIndexSectionStartMarker) {
		t.Fatalf("block should start with start marker: %q", block)
	}
	if !strings.Contains(block, FactIndexSectionEndMarker) {
		t.Fatalf("block should include end marker: %q", block)
	}
}
