package presets

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

func TestRolePresetCatalogReferencesIdentityAssets(t *testing.T) {
	raw, err := os.ReadFile(filepath.Join("assets", "roles", "catalog.yaml"))
	if err != nil {
		t.Fatalf("read catalog: %v", err)
	}
	catalog := string(raw)
	if strings.Contains(catalog, "definition: |") {
		t.Errorf("catalog must use definition_path references, got inline definition")
	}
	if !strings.Contains(catalog, "definition_path: assets/roles/definitions/backend-developer/en-US/IDENTITY.md") {
		t.Errorf("catalog missing backend-developer en-US definition_path")
	}

	definition, err := os.ReadFile(filepath.Join("assets", "roles", "definitions", "backend-developer", "en-US", "IDENTITY.md"))
	if err != nil {
		t.Fatalf("read backend-developer identity: %v", err)
	}
	def := string(definition)
	if !strings.Contains(def, "## Implementation Checks") {
		t.Errorf("backend-developer en-US identity missing Implementation Checks section")
	}
	if !strings.Contains(def, "Do not broaden into unrelated refactors") {
		t.Errorf("backend-developer en-US identity missing scope guidance")
	}
}

func TestRolePresetsReturnsLocalizedEntries(t *testing.T) {
	for _, locale := range []string{"zh-CN", "en-US"} {
		presets, err := RolePresets(locale)
		if err != nil {
			t.Fatalf("RolePresets(%q): %v", locale, err)
		}
		if len(presets) == 0 {
			t.Fatalf("RolePresets(%q) returned no presets", locale)
		}
		ids := make([]string, 0, len(presets))
		for _, preset := range presets {
			if preset.Locale != locale {
				t.Errorf("preset %q locale = %q, want %q", preset.ID, preset.Locale, locale)
			}
			if preset.Name == "" {
				t.Errorf("preset %q has empty Name", preset.ID)
			}
			if preset.Definition == "" {
				t.Errorf("preset %q has empty Definition", preset.ID)
			}
			ids = append(ids, preset.ID)
		}
		if dup := firstDuplicate(ids); dup != "" {
			t.Errorf("RolePresets(%q) returned duplicate id %q", locale, dup)
		}
	}
}

func TestRolePresetsFallsBackToFamilyThenEnUS(t *testing.T) {
	zhTW, err := RolePresets("zh-TW")
	if err != nil {
		t.Fatalf("RolePresets(zh-TW): %v", err)
	}
	if len(zhTW) == 0 {
		t.Fatalf("RolePresets(zh-TW) returned no presets")
	}
	for _, preset := range zhTW {
		if preset.Locale != "zh-CN" {
			t.Errorf("family fallback preset %q locale = %q, want zh-CN", preset.ID, preset.Locale)
		}
	}

	empty, err := RolePresets("")
	if err != nil {
		t.Fatalf("RolePresets(%q): %v", "", err)
	}
	for _, preset := range empty {
		if preset.Locale != "en-US" {
			t.Errorf("default-locale preset %q locale = %q, want en-US", preset.ID, preset.Locale)
		}
	}
}

func TestRolePresetsSortedByID(t *testing.T) {
	presets, err := RolePresets("en-US")
	if err != nil {
		t.Fatalf("RolePresets(en-US): %v", err)
	}
	ids := make([]string, 0, len(presets))
	for _, preset := range presets {
		ids = append(ids, preset.ID)
	}
	if !slices.IsSorted(ids) {
		t.Errorf("RolePresets must be sorted by ID; got %v", ids)
	}
}

func TestRolePresetByIDFound(t *testing.T) {
	preset, ok, err := RolePresetByID("research-agent", "zh-CN")
	if err != nil {
		t.Fatalf("RolePresetByID: %v", err)
	}
	if !ok {
		t.Fatal("expected preset to be found")
	}
	if preset.ID != "research-agent" {
		t.Errorf("ID = %q, want research-agent", preset.ID)
	}
	if preset.Locale != "zh-CN" {
		t.Errorf("Locale = %q, want zh-CN", preset.Locale)
	}
	if preset.Definition == "" {
		t.Error("Definition is empty")
	}
}

func TestTestingAssistantPresetIsDistinctFromQA(t *testing.T) {
	zhPreset, ok, err := RolePresetByID("testing-assistant", "zh-CN")
	if err != nil {
		t.Fatalf("RolePresetByID(testing-assistant, zh-CN): %v", err)
	}
	if !ok {
		t.Fatal("expected testing-assistant preset to be found")
	}
	if zhPreset.Name != "测试助手" {
		t.Errorf("Name = %q, want 测试助手", zhPreset.Name)
	}
	if zhPreset.Scenario != "testing" {
		t.Errorf("Scenario = %q, want testing", zhPreset.Scenario)
	}
	if !slices.Contains(zhPreset.Tags, "testing") {
		t.Errorf("Tags = %v, want testing tag", zhPreset.Tags)
	}
	for _, expected := range []string{"测试计划", "测试用例", "功能测试", "回归测试", "接口测试", "缺陷复现"} {
		if !strings.Contains(zhPreset.Definition, expected) {
			t.Errorf("testing-assistant zh-CN definition missing %q", expected)
		}
	}

	enPreset, ok, err := RolePresetByID("testing-assistant", "en-US")
	if err != nil {
		t.Fatalf("RolePresetByID(testing-assistant, en-US): %v", err)
	}
	if !ok {
		t.Fatal("expected testing-assistant en-US preset to be found")
	}
	if enPreset.Name != "Testing Assistant" {
		t.Errorf("Name = %q, want Testing Assistant", enPreset.Name)
	}

	qaPreset, ok, err := RolePresetByID("qa-engineer", "zh-CN")
	if err != nil {
		t.Fatalf("RolePresetByID(qa-engineer, zh-CN): %v", err)
	}
	if !ok {
		t.Fatal("expected existing qa-engineer preset to remain available")
	}
	if qaPreset.Name != "QA 助手" {
		t.Errorf("QA Name = %q, want QA 助手", qaPreset.Name)
	}
	if qaPreset.Description == zhPreset.Description {
		t.Error("testing-assistant must not reuse the QA assistant positioning")
	}
}

func TestRolePresetByIDMissing(t *testing.T) {
	_, ok, err := RolePresetByID("does-not-exist", "en-US")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if ok {
		t.Error("expected ok=false for unknown id")
	}
}

func TestRolePresetByIDRespectsLocaleFallback(t *testing.T) {
	preset, ok, err := RolePresetByID("research-agent", "zh-TW")
	if err != nil {
		t.Fatalf("RolePresetByID: %v", err)
	}
	if !ok {
		t.Fatal("expected preset to be found via family fallback")
	}
	if preset.Locale != "zh-CN" {
		t.Errorf("Locale = %q, want zh-CN", preset.Locale)
	}
}

func TestRolePresetBackendDeveloperCarriesImplementationChecks(t *testing.T) {
	preset, ok, err := RolePresetByID("backend-developer", "en-US")
	if err != nil {
		t.Fatalf("RolePresetByID: %v", err)
	}
	if !ok {
		t.Fatal("expected backend-developer preset to be found")
	}
	if preset.Scenario == "" {
		t.Error("Scenario is empty")
	}
	if !strings.Contains(preset.Definition, "## Implementation Checks") {
		t.Errorf("backend-developer en-US identity missing Implementation Checks section")
	}
}

func TestFrontendDeveloperPresetResolvesOfficialSkillReferencesAsPortableCopies(t *testing.T) {
	preset, ok, err := RolePresetByID("frontend-developer", "zh-CN")
	if err != nil {
		t.Fatalf("RolePresetByID(frontend-developer): %v", err)
	}
	if !ok {
		t.Fatal("expected frontend-developer preset")
	}
	want := []string{
		"frontend-ui-engineering",
		"browser-testing-with-devtools",
	}
	got := make([]string, 0, len(preset.Attachments))
	for _, attachment := range preset.Attachments {
		if attachment.Type != "skill" {
			t.Fatalf("attachment %q Type = %q, want skill", attachment.Name, attachment.Type)
		}
		if attachment.Realization != "copy" {
			t.Fatalf("attachment %q Realization = %q, want copy", attachment.Name, attachment.Realization)
		}
		if attachment.Required {
			t.Fatalf("attachment %q Required = true, want false", attachment.Name)
		}
		if attachment.ResourceID != "" {
			t.Fatalf("attachment %q must be portable content, got ResourceID %q", attachment.Name, attachment.ResourceID)
		}
		if !strings.Contains(attachment.Content, "SKILL") && !strings.Contains(attachment.Content, "#") {
			t.Fatalf("attachment %q missing SKILL.md content", attachment.Name)
		}
		if attachment.Description == "" {
			t.Fatalf("attachment %q missing frontmatter description", attachment.Name)
		}
		got = append(got, attachment.Name)
	}
	if !slices.Equal(got, want) {
		t.Fatalf("frontend-developer skill attachments = %#v, want %#v", got, want)
	}
}

func TestOtherPresetRolesDoNotGainDefaultSkills(t *testing.T) {
	presets, err := RolePresets("en-US")
	if err != nil {
		t.Fatalf("RolePresets: %v", err)
	}
	for _, preset := range presets {
		if preset.ID == "frontend-developer" {
			continue
		}
		for _, attachment := range preset.Attachments {
			if attachment.Type == "skill" {
				t.Fatalf("preset %q unexpectedly has skill attachment %q", preset.ID, attachment.Name)
			}
		}
	}
}

func TestRolePresetsReturnedTagsAndAttachmentsAreNotSharedWithCache(t *testing.T) {
	first, err := RolePresets("en-US")
	if err != nil {
		t.Fatalf("RolePresets(en-US): %v", err)
	}
	if len(first) == 0 {
		t.Fatal("RolePresets(en-US) returned no presets")
	}
	target := first[0]
	tagMutation := "__mutated_tag__"
	attachmentMutation := "__mutated_attachment__"
	attachmentFieldMutated := false
	tagFieldMutated := false
	if len(target.Tags) > 0 {
		target.Tags[0] = tagMutation
		tagFieldMutated = true
	}
	if len(target.Attachments) > 0 {
		target.Attachments[0].Name = attachmentMutation
		attachmentFieldMutated = true
	}

	second, err := RolePresets("en-US")
	if err != nil {
		t.Fatalf("second RolePresets(en-US): %v", err)
	}
	for _, preset := range second {
		for _, tag := range preset.Tags {
			if tag == tagMutation {
				t.Errorf("Tag mutation leaked into cache for preset %q", preset.ID)
			}
		}
		for _, attachment := range preset.Attachments {
			if attachment.Name == attachmentMutation {
				t.Errorf("Attachment.Name mutation leaked into cache for preset %q", preset.ID)
			}
		}
	}
	if !tagFieldMutated {
		t.Skip("no preset had Tags to mutate; catalog should define at least one tagged preset")
	}
	if !attachmentFieldMutated {
		t.Skip("no preset had Attachments to mutate; catalog should define at least one preset with attachments")
	}
}

func TestRolePresetByIDReturnedTagsAndAttachmentsAreNotSharedWithCache(t *testing.T) {
	const id = "research-agent"
	first, ok, err := RolePresetByID(id, "en-US")
	if err != nil {
		t.Fatalf("RolePresetByID: %v", err)
	}
	if !ok {
		t.Fatalf("expected preset %q to be found", id)
	}
	tagMutation := "__mutated_tag_byid__"
	attachmentMutation := "__mutated_attachment_byid__"
	tagFieldMutated := false
	attachmentFieldMutated := false
	if len(first.Tags) > 0 {
		first.Tags[0] = tagMutation
		tagFieldMutated = true
	}
	if len(first.Attachments) > 0 {
		first.Attachments[0].Name = attachmentMutation
		attachmentFieldMutated = true
	}

	second, ok, err := RolePresetByID(id, "en-US")
	if err != nil {
		t.Fatalf("second RolePresetByID: %v", err)
	}
	if !ok {
		t.Fatalf("expected preset %q to be found on re-fetch", id)
	}
	for _, tag := range second.Tags {
		if tag == tagMutation {
			t.Errorf("Tag mutation leaked into cache for preset %q", id)
		}
	}
	for _, attachment := range second.Attachments {
		if attachment.Name == attachmentMutation {
			t.Errorf("Attachment.Name mutation leaked into cache for preset %q", id)
		}
	}
	if !tagFieldMutated {
		t.Skip("preset had no Tags to mutate")
	}
	if !attachmentFieldMutated {
		t.Skip("preset had no Attachments to mutate")
	}
}

func firstDuplicate(values []string) string {
	seen := map[string]struct{}{}
	for _, v := range values {
		if _, ok := seen[v]; ok {
			return v
		}
		seen[v] = struct{}{}
	}
	return ""
}
