package promptfs

import (
	"io/fs"
	"strings"
	"testing"
	"testing/fstest"
)

func TestModulePromptFSCombinesModuleAndSharedPrompts(t *testing.T) {
	sharedFS := fstest.MapFS{
		"shared/system.md":     {Data: []byte("system")},
		"shared/reference.md":  {Data: []byte("reference")},
		"nested/transcript.md": {Data: []byte("transcript")},
	}
	fsys, err := ModulePromptFS("module.test", fstest.MapFS{
		"assets/prompts/module.test.yaml": {Data: []byte("id: module.test")},
		"assets/prompts/task.md":          {Data: []byte("task")},
	}, []ModulePromptFile{
		{Name: "module.test.yaml", Path: "assets/prompts/module.test.yaml"},
		{Name: "task.md", Path: "assets/prompts/task.md"},
	},
		SharedPromptFile{Name: "system.md", FS: sharedFS, Path: "shared/system.md"},
		SharedPromptFile{Name: "reference.md", FS: sharedFS, Path: "shared/reference.md"},
		SharedPromptFile{Name: "transcript.md", FS: sharedFS, Path: "nested/transcript.md"},
	)
	if err != nil {
		t.Fatalf("ModulePromptFS() error = %v, want nil", err)
	}

	tests := map[string]string{
		"assets/prompts/module.test/module.test.yaml":           "id: module.test",
		"assets/prompts/module.test/task.md":                    "task",
		"assets/prompts/module.test/sharedassets/system.md":     "system",
		"assets/prompts/module.test/sharedassets/reference.md":  "reference",
		"assets/prompts/module.test/sharedassets/transcript.md": "transcript",
	}
	for path, want := range tests {
		data, err := fs.ReadFile(fsys, path)
		if err != nil {
			t.Fatalf("ReadFile(%q) error = %v, want nil", path, err)
		}
		if string(data) != want {
			t.Fatalf("ReadFile(%q) = %q, want %q", path, data, want)
		}
	}

	entries, err := fs.ReadDir(fsys, ".")
	if err != nil {
		t.Fatalf("ReadDir(.) error = %v, want nil", err)
	}
	if len(entries) != 1 || entries[0].Name() != "assets" || !entries[0].IsDir() {
		t.Fatalf("ReadDir(.) = %#v, want assets directory", entries)
	}
	entries, err = fs.ReadDir(fsys, "assets/prompts/module.test/sharedassets")
	if err != nil {
		t.Fatalf("ReadDir(sharedassets) error = %v, want nil", err)
	}
	if len(entries) != 3 || entries[0].Name() != "reference.md" || entries[1].Name() != "system.md" || entries[2].Name() != "transcript.md" {
		t.Fatalf("ReadDir(sharedassets) = %#v, want sorted shared prompt entries", entries)
	}
}

func TestModulePromptFSRejectsMissingModuleFile(t *testing.T) {
	_, err := ModulePromptFS("module.test", fstest.MapFS{}, []ModulePromptFile{
		{Name: "task.md", Path: "assets/prompts/task.md"},
	})
	if err == nil || !strings.Contains(err.Error(), "read module prompt asset assets/prompts/task.md") {
		t.Fatalf("ModulePromptFS() error = %v, want missing module asset context", err)
	}
}

func TestModulePromptFSRejectsMissingSharedFile(t *testing.T) {
	_, err := ModulePromptFS("module.test", fstest.MapFS{
		"assets/prompts/task.md": {Data: []byte("task")},
	}, []ModulePromptFile{
		{Name: "task.md", Path: "assets/prompts/task.md"},
	}, SharedPromptFile{Name: "system.md", FS: fstest.MapFS{}, Path: "shared/system.md"})
	if err == nil || !strings.Contains(err.Error(), "read shared prompt asset shared/system.md") {
		t.Fatalf("ModulePromptFS() error = %v, want missing shared asset context", err)
	}
}

func TestModulePromptFSRejectsNestedVirtualFileName(t *testing.T) {
	_, err := ModulePromptFS("module.test", fstest.MapFS{
		"assets/prompts/task.md": {Data: []byte("task")},
	}, []ModulePromptFile{
		{Name: "nested/task.md", Path: "assets/prompts/task.md"},
	})
	if err == nil || !strings.Contains(err.Error(), "must not contain path separators") {
		t.Fatalf("ModulePromptFS() error = %v, want nested file name error", err)
	}
}

func TestModulePromptFSRejectsNestedSharedFileName(t *testing.T) {
	_, err := ModulePromptFS("module.test", fstest.MapFS{
		"assets/prompts/task.md": {Data: []byte("task")},
	}, []ModulePromptFile{
		{Name: "task.md", Path: "assets/prompts/task.md"},
	}, SharedPromptFile{Name: "nested/system.md", FS: fstest.MapFS{}, Path: "shared/system.md"})
	if err == nil || !strings.Contains(err.Error(), "shared prompt file name") || !strings.Contains(err.Error(), "must not contain path separators") {
		t.Fatalf("ModulePromptFS() error = %v, want nested shared file name error", err)
	}
}

func TestModulePromptFSRejectsDuplicateNormalizedDestinations(t *testing.T) {
	for _, test := range []struct {
		name string
		call func() error
		want string
	}{
		{
			name: "module",
			call: func() error {
				_, err := ModulePromptFS("module.test", fstest.MapFS{
					"assets/prompts/first.md":  {Data: []byte("first")},
					"assets/prompts/second.md": {Data: []byte("second")},
				}, []ModulePromptFile{
					{Name: "task.md", Path: "assets/prompts/first.md"},
					{Name: " task.md ", Path: "assets/prompts/second.md"},
				})
				return err
			},
			want: `duplicate module prompt destination "assets/prompts/module.test/task.md"`,
		},
		{
			name: "sharedassets",
			call: func() error {
				shared := fstest.MapFS{
					"shared/first.md":  {Data: []byte("first")},
					"shared/second.md": {Data: []byte("second")},
				}
				_, err := ModulePromptFS("module.test", fstest.MapFS{}, nil,
					SharedPromptFile{Name: "task.md", FS: shared, Path: "shared/first.md"},
					SharedPromptFile{Name: " task.md ", FS: shared, Path: "shared/second.md"},
				)
				return err
			},
			want: `duplicate sharedassets prompt destination "assets/prompts/module.test/sharedassets/task.md"`,
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			if err := test.call(); err == nil || err.Error() != test.want {
				t.Fatalf("ModulePromptFS() error = %v, want %q", err, test.want)
			}
		})
	}
}

func TestModulePromptFSAllowsMatchingModuleAndSharedBasenames(t *testing.T) {
	fsys, err := ModulePromptFS("module.test", fstest.MapFS{
		"assets/prompts/system.md": {Data: []byte("module")},
	}, []ModulePromptFile{{Name: "system.md", Path: "assets/prompts/system.md"}},
		SharedPromptFile{Name: "system.md", FS: fstest.MapFS{"shared/system.md": {Data: []byte("shared")}}, Path: "shared/system.md"},
	)
	if err != nil {
		t.Fatal(err)
	}
	for name, want := range map[string]string{
		"assets/prompts/module.test/system.md":              "module",
		"assets/prompts/module.test/sharedassets/system.md": "shared",
	} {
		data, err := fs.ReadFile(fsys, name)
		if err != nil || string(data) != want {
			t.Fatalf("ReadFile(%q) = %q, %v; want %q", name, data, err, want)
		}
	}
}

func TestModulePromptFSRejectsInvalidModuleDir(t *testing.T) {
	_, err := ModulePromptFS(".", fstest.MapFS{}, nil)
	if err == nil || !strings.Contains(err.Error(), "module prompt directory") {
		t.Fatalf("ModulePromptFS() error = %v, want module directory context", err)
	}
}

func TestModulePromptFSRejectsNilFilesystems(t *testing.T) {
	_, err := ModulePromptFS("module.test", nil, nil)
	if err == nil || !strings.Contains(err.Error(), "module prompt filesystem must not be nil") {
		t.Fatalf("ModulePromptFS() error = %v, want nil module filesystem error", err)
	}

	_, err = ModulePromptFS("module.test", fstest.MapFS{}, nil, SharedPromptFile{Name: "system.md", Path: "shared/system.md"})
	if err == nil || !strings.Contains(err.Error(), "shared prompt file \"system.md\" filesystem must not be nil") {
		t.Fatalf("ModulePromptFS() error = %v, want nil shared filesystem error", err)
	}
}
