package fs

import (
	"context"
	"os"
	"path/filepath"
	"testing"

	"codeberg.org/VARASYS/ZDDC/zddc/internal/zddc"
)

func setupTreeRoot(t *testing.T) string {
	t.Helper()
	root := t.TempDir()
	// Permissive root .zddc so subdirectory ACL checks pass.
	if err := os.WriteFile(filepath.Join(root, ".zddc"),
		[]byte("acl:\n  permissions:\n    \"*@example.com\": rwcda\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	zddc.InvalidateCache(root)
	return root
}

func TestListDirectory_VirtualUserHome_AppearsWhenMissing(t *testing.T) {
	root := setupTreeRoot(t)
	if err := os.MkdirAll(filepath.Join(root, "Proj", "working"), 0o755); err != nil {
		t.Fatal(err)
	}
	zddc.InvalidateCache(root)

	got, err := ListDirectory(context.Background(), nil, root, "Proj/working", "alice@example.com", "/Proj/working/")
	if err != nil {
		t.Fatalf("list: %v", err)
	}

	var virtual *string
	for i := range got {
		if got[i].Virtual {
			n := got[i].Name
			virtual = &n
		}
	}
	if virtual == nil {
		t.Fatalf("expected synthetic <viewer-email>/ entry, got entries: %+v", got)
	}
	if *virtual != "alice@example.com/" {
		t.Errorf("synthetic name = %q, want alice@example.com/", *virtual)
	}
}

func TestListDirectory_VirtualUserHome_SuppressedWhenRealExists(t *testing.T) {
	root := setupTreeRoot(t)
	// A real folder exists for the viewer (any case).
	if err := os.MkdirAll(filepath.Join(root, "Proj", "working", "Alice@Example.com"), 0o755); err != nil {
		t.Fatal(err)
	}
	zddc.InvalidateCache(root)

	got, err := ListDirectory(context.Background(), nil, root, "Proj/working", "alice@example.com", "/Proj/working/")
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	for _, fi := range got {
		if fi.Virtual {
			t.Errorf("synthetic entry should be suppressed when a case-fold match exists; got %+v", fi)
		}
	}
}

func TestListDirectory_VirtualUserHome_AnonymousNoEntry(t *testing.T) {
	root := setupTreeRoot(t)
	if err := os.MkdirAll(filepath.Join(root, "Proj", "working"), 0o755); err != nil {
		t.Fatal(err)
	}
	zddc.InvalidateCache(root)

	got, err := ListDirectory(context.Background(), nil, root, "Proj/working", "" /* no viewer */, "/Proj/working/")
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	for _, fi := range got {
		if fi.Virtual {
			t.Errorf("anonymous viewer should not see synthetic entries; got %+v", fi)
		}
	}
}

func TestListDirectory_VirtualUserHome_OutsideWorkingNoEntry(t *testing.T) {
	root := setupTreeRoot(t)
	if err := os.MkdirAll(filepath.Join(root, "Proj", "staging"), 0o755); err != nil {
		t.Fatal(err)
	}
	zddc.InvalidateCache(root)

	got, err := ListDirectory(context.Background(), nil, root, "Proj/staging", "alice@example.com", "/Proj/staging/")
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	for _, fi := range got {
		if fi.Virtual {
			t.Errorf("staging/ should not have a synthetic user-home entry; got %+v", fi)
		}
	}
}

func TestListDirectory_VirtualUserHome_DeepWorkingNoEntry(t *testing.T) {
	root := setupTreeRoot(t)
	// Listing inside working/ at depth 3+ — no synthetic entry should fire.
	if err := os.MkdirAll(filepath.Join(root, "Proj", "working", "alice@example.com"), 0o755); err != nil {
		t.Fatal(err)
	}
	zddc.InvalidateCache(root)

	got, err := ListDirectory(context.Background(), nil, root,
		"Proj/working/alice@example.com", "alice@example.com",
		"/Proj/working/alice@example.com/")
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	for _, fi := range got {
		if fi.Virtual {
			t.Errorf("nested working/ subdir must not synthesise the user home; got %+v", fi)
		}
	}
}

func TestListDirectory_VirtualUserHome_CaseFoldWorking(t *testing.T) {
	root := setupTreeRoot(t)
	// Pre-existing PascalCase Working/.
	if err := os.MkdirAll(filepath.Join(root, "Proj", "Working"), 0o755); err != nil {
		t.Fatal(err)
	}
	zddc.InvalidateCache(root)

	got, err := ListDirectory(context.Background(), nil, root, "Proj/Working", "alice@example.com", "/Proj/Working/")
	if err != nil {
		t.Fatalf("list: %v", err)
	}
	var found bool
	for _, fi := range got {
		if fi.Virtual {
			found = true
		}
	}
	if !found {
		t.Errorf("PascalCase Working/ should still surface the synthetic entry; got entries: %+v", got)
	}
}

// Listing a canonical project folder that doesn't exist on disk yet
// returns an empty slice instead of os.ErrNotExist. The stage-strip
// nav links into <project>/working/ etc. unconditionally; this keeps
// fresh projects (no working/ on disk yet) from 404'ing.
func TestListDirectory_CanonicalProjectFolder_EmptyWhenMissing(t *testing.T) {
	root := setupTreeRoot(t)
	// Proj exists but Proj/working/ does NOT.
	if err := os.MkdirAll(filepath.Join(root, "Proj"), 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(root, "Proj", ".zddc"),
		[]byte("acl:\n  permissions:\n    \"*@example.com\": rwcda\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	zddc.InvalidateCache(root)

	for _, stage := range []string{"working", "staging", "reviewing", "archive"} {
		got, err := ListDirectory(context.Background(), nil, root,
			"Proj/"+stage, "alice@example.com", "/Proj/"+stage+"/")
		if err != nil {
			t.Errorf("ListDirectory(Proj/%s) on missing dir: err = %v, want nil", stage, err)
			continue
		}
		// working/ surfaces a synthetic <viewer-email>/ entry; the others
		// should be a flat empty listing.
		if stage == "working" {
			if len(got) != 1 || !got[0].Virtual {
				t.Errorf("ListDirectory(Proj/working) on missing dir: got %+v, want only the virtual home entry", got)
			}
		} else {
			if len(got) != 0 {
				t.Errorf("ListDirectory(Proj/%s) on missing dir: got %+v, want empty", stage, got)
			}
		}
	}
}

// Non-canonical paths still 404 (return os.ErrNotExist) — the fallback
// only applies to the four canonical project-root folders.
func TestListDirectory_NonCanonicalMissing_StillNotFound(t *testing.T) {
	root := setupTreeRoot(t)
	if err := os.MkdirAll(filepath.Join(root, "Proj"), 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(root, "Proj", ".zddc"),
		[]byte("acl:\n  permissions:\n    \"*@example.com\": rwcda\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	zddc.InvalidateCache(root)

	_, err := ListDirectory(context.Background(), nil, root,
		"Proj/random-folder-that-doesnt-exist", "alice@example.com",
		"/Proj/random-folder-that-doesnt-exist/")
	if !os.IsNotExist(err) {
		t.Errorf("ListDirectory on a non-canonical missing path: err = %v, want IsNotExist", err)
	}
}
