package project

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

func TestTopDataAssetsDirPrefersExplicitConfig(t *testing.T) {
	root := t.TempDir()
	mkdirAll(t, filepath.Join(root, "src"))
	mkdirAll(t, filepath.Join(root, "assets"))
	mkdirAll(t, filepath.Join(root, "build"))
	mkdirAll(t, filepath.Join(root, "custom-assets"))

	proj := &Project{
		Root: root,
		Config: Config{
			Module: ModuleConfig{Name: "Test", ResRef: "test"},
			Paths:  PathConfig{Source: "src", Assets: "assets", Build: "build"},
			TopData: TopDataConfig{
				Assets: "custom-assets",
			},
		},
	}

	result := proj.TopDataAssetsDir()
	expected := filepath.Join(root, "custom-assets")
	if result != expected {
		t.Errorf("expected %s, got %s", expected, result)
	}
}

func TestTopDataAssetsDirDoesNotAssumeSiblingRepo(t *testing.T) {
	root := t.TempDir()
	mkdirAll(t, filepath.Join(root, "src"))
	mkdirAll(t, filepath.Join(root, "assets"))
	mkdirAll(t, filepath.Join(root, "build"))

	parentDir := filepath.Dir(root)
	siblingPath := filepath.Join(parentDir, "sow-assets")
	mkdirAll(t, siblingPath)
	mkdirAll(t, filepath.Join(siblingPath, "assets", "part", "belt"))

	proj := &Project{
		Root: root,
		Config: Config{
			Module: ModuleConfig{Name: "Test", ResRef: "test"},
			Paths:  PathConfig{Source: "src", Assets: "assets", Build: "build"},
			TopData: TopDataConfig{
				Assets: "sow-assets",
			},
		},
	}

	result := proj.TopDataAssetsDir()
	expected := filepath.Join(root, "sow-assets")
	if result != expected {
		t.Errorf("expected unresolved local path %s, got %s", expected, result)
	}
}

func TestTopDataAssetsDirReturnsEmptyForNoConfig(t *testing.T) {
	root := t.TempDir()
	mkdirAll(t, filepath.Join(root, "src"))
	mkdirAll(t, filepath.Join(root, "assets"))
	mkdirAll(t, filepath.Join(root, "build"))

	proj := &Project{
		Root: root,
		Config: Config{
			Module:  ModuleConfig{Name: "Test", ResRef: "test"},
			Paths:   PathConfig{Source: "src", Assets: "assets", Build: "build"},
			TopData: TopDataConfig{},
		},
	}

	result := proj.TopDataAssetsDir()
	if result != "" {
		t.Errorf("expected empty string, got %s", result)
	}
}

func TestValidateLayoutAllowsMissingAssetsDir(t *testing.T) {
	root := t.TempDir()
	mkdirAll(t, filepath.Join(root, "src"))
	mkdirAll(t, filepath.Join(root, "build"))

	proj := &Project{
		Root: root,
		Config: Config{
			Module: ModuleConfig{Name: "Test", ResRef: "test"},
			Paths:  PathConfig{Source: "src", Assets: "assets", Build: "build"},
		},
	}

	if err := proj.ValidateLayout(); err != nil {
		t.Fatalf("ValidateLayout returned error: %v", err)
	}
	if _, err := os.Stat(filepath.Join(root, "assets")); !errors.Is(err, os.ErrNotExist) {
		t.Fatalf("expected missing assets dir to remain absent, got err=%v", err)
	}
}

func TestScanAllowsMissingAssetsDir(t *testing.T) {
	root := t.TempDir()
	mkdirAll(t, filepath.Join(root, "src"))
	mkdirAll(t, filepath.Join(root, "build"))

	proj := &Project{
		Root: root,
		Config: Config{
			Module: ModuleConfig{Name: "Test", ResRef: "test"},
			Paths:  PathConfig{Source: "src", Assets: "assets", Build: "build"},
		},
	}

	if err := proj.Scan(); err != nil {
		t.Fatalf("Scan returned error: %v", err)
	}
	if len(proj.Inventory.AssetFiles) != 0 {
		t.Fatalf("expected no asset files, got %#v", proj.Inventory.AssetFiles)
	}
}

func mkdirAll(t *testing.T, path string) {
	t.Helper()
	if err := os.MkdirAll(path, 0o755); err != nil {
		t.Fatalf("mkdir %s: %v", path, err)
	}
}

func TestCloneWithHAKNamesFiltersConfiguredHAKs(t *testing.T) {
	proj := &Project{
		Root: "/tmp/test",
		Config: Config{
			Module: ModuleConfig{Name: "Test", ResRef: "test"},
			Paths:  PathConfig{Source: "src", Assets: "assets", Build: "build"},
			HAKs: []HAKConfig{
				{Name: "sow_over", Priority: 1, Include: []string{"over/**"}},
				{Name: "sow_core", Priority: 2, Include: []string{"core/**"}},
				{Name: "sow_item", Priority: 3, Include: []string{"item/**"}},
			},
		},
	}

	filtered, err := proj.CloneWithHAKNames([]string{"sow_core", "sow_item"})
	if err != nil {
		t.Fatalf("CloneWithHAKNames returned error: %v", err)
	}

	if filtered == proj {
		t.Fatalf("expected filtered clone, got original pointer")
	}

	got := make([]string, 0, len(filtered.Config.HAKs))
	for _, hak := range filtered.Config.HAKs {
		got = append(got, hak.Name)
	}
	want := []string{"sow_core", "sow_item"}
	if !slices.Equal(got, want) {
		t.Fatalf("unexpected filtered haks: got %v want %v", got, want)
	}

	if len(proj.Config.HAKs) != 3 {
		t.Fatalf("original project config should remain unchanged, got %d haks", len(proj.Config.HAKs))
	}
}

func TestCloneWithHAKNamesRejectsUnknownHAKs(t *testing.T) {
	proj := &Project{
		Config: Config{
			HAKs: []HAKConfig{
				{Name: "sow_core", Priority: 1, Include: []string{"core/**"}},
			},
		},
	}

	if _, err := proj.CloneWithHAKNames([]string{"missing_hak"}); err == nil {
		t.Fatal("expected error for unknown hak name")
	}
}
