package zddc

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

func TestEnsureCanonicalAncestors_LazyCreation(t *testing.T) {
	root := t.TempDir()
	target := filepath.Join(root, "Proj", "working", "alice@x.com", "notes.md")

	resolved, err := EnsureCanonicalAncestors(root, target, "alice@x.com", 0o755)
	if err != nil {
		t.Fatalf("ensure: %v", err)
	}
	if resolved != target {
		t.Errorf("resolved=%q, target=%q (no case variant exists, should be identical)", resolved, target)
	}

	// working/ is now created with auto-own .zddc.
	autoZ := filepath.Join(root, "Proj", "working", ".zddc")
	data, err := os.ReadFile(autoZ)
	if err != nil {
		t.Fatalf("auto-own .zddc not written at working/: %v", err)
	}
	body := string(data)
	if !strings.Contains(body, "alice@x.com: rwcda") {
		t.Errorf("auto-own grant missing: %s", body)
	}
	if !strings.Contains(body, "created_by: alice@x.com") {
		t.Errorf("created_by missing: %s", body)
	}

	// alice@x.com/ subfolder gets a FENCED auto-own .zddc — private by
	// default so other users can't read alice's drafts via ancestor
	// cascade. alice can edit the file later to add collaborators.
	homeZddc := filepath.Join(root, "Proj", "working", "alice@x.com", ".zddc")
	if _, err := os.Stat(filepath.Join(root, "Proj", "working", "alice@x.com")); err != nil {
		t.Errorf("subfolder not created: %v", err)
	}
	homeData, err := os.ReadFile(homeZddc)
	if err != nil {
		t.Fatalf("per-user-home auto-own .zddc not written: %v", err)
	}
	homeBody := string(homeData)
	if !strings.Contains(homeBody, "alice@x.com: rwcda") {
		t.Errorf("per-user-home grant missing: %s", homeBody)
	}
	if !strings.Contains(homeBody, "inherit: false") {
		t.Errorf("per-user-home .zddc should have inherit: false; got: %s", homeBody)
	}
}

// staging/<folder>/ is NOT fenced — staging is a shared lane (transmittal
// folders are date+tracking-named, not per-user). Only per-user homes
// under working/ get the fence.
func TestEnsureCanonicalAncestors_StagingChildNotFenced(t *testing.T) {
	root := t.TempDir()
	target := filepath.Join(root, "Proj", "staging",
		"2025-10-31_proj-EM-TRN-0042 (RSA) - Outbound", "doc.pdf")
	if _, err := EnsureCanonicalAncestors(root, target, "alice@x.com", 0o755); err != nil {
		t.Fatalf("ensure: %v", err)
	}
	// staging/<folder>/.zddc should not exist (only the parent staging/
	// gets an auto-own; the date-named child is plain).
	childZddc := filepath.Join(root, "Proj", "staging",
		"2025-10-31_proj-EM-TRN-0042 (RSA) - Outbound", ".zddc")
	if _, err := os.Stat(childZddc); !os.IsNotExist(err) {
		t.Errorf("staging child should NOT have auto-own .zddc; got err=%v", err)
	}
}

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

	target := filepath.Join(root, "Proj", "working", "foo.md")
	resolved, err := EnsureCanonicalAncestors(root, target, "alice@x.com", 0o755)
	if err != nil {
		t.Fatalf("ensure: %v", err)
	}

	// Resolved path uses on-disk Working/ casing.
	want := filepath.Join(root, "Proj", "Working", "foo.md")
	if resolved != want {
		t.Errorf("resolved=%q, want %q", resolved, want)
	}

	// No new working/ sibling.
	if _, err := os.Stat(filepath.Join(root, "Proj", "working")); !os.IsNotExist(err) {
		t.Errorf("lowercase sibling should not exist; got err=%v", err)
	}

	// Working/ already existed before our call — no auto-own .zddc was
	// retroactively written.
	if _, err := os.Stat(filepath.Join(root, "Proj", "Working", ".zddc")); !os.IsNotExist(err) {
		t.Errorf("auto-own .zddc should not be written to a pre-existing folder; got err=%v", err)
	}
}

func TestEnsureCanonicalAncestors_PerPartyIncoming(t *testing.T) {
	root := t.TempDir()
	target := filepath.Join(root, "Proj", "archive", "ACME", "incoming", "submission.pdf")

	_, err := EnsureCanonicalAncestors(root, target, "rep@acme.com", 0o755)
	if err != nil {
		t.Fatalf("ensure: %v", err)
	}

	// archive/ created (no auto-own).
	if _, err := os.Stat(filepath.Join(root, "Proj", "archive")); err != nil {
		t.Errorf("archive/ not created: %v", err)
	}
	if _, err := os.Stat(filepath.Join(root, "Proj", "archive", ".zddc")); !os.IsNotExist(err) {
		t.Errorf("archive/ should not have auto-own .zddc; got err=%v", err)
	}

	// archive/ACME/ created (no auto-own — it's a party folder, not canonical).
	if _, err := os.Stat(filepath.Join(root, "Proj", "archive", "ACME")); err != nil {
		t.Errorf("ACME/ not created: %v", err)
	}
	if _, err := os.Stat(filepath.Join(root, "Proj", "archive", "ACME", ".zddc")); !os.IsNotExist(err) {
		t.Errorf("ACME/ should not have auto-own .zddc; got err=%v", err)
	}

	// archive/ACME/incoming/ created WITH auto-own.
	autoZ := filepath.Join(root, "Proj", "archive", "ACME", "incoming", ".zddc")
	data, err := os.ReadFile(autoZ)
	if err != nil {
		t.Fatalf("auto-own .zddc at incoming/ not written: %v", err)
	}
	if !strings.Contains(string(data), "rep@acme.com: rwcda") {
		t.Errorf("incoming/ auto-own missing rep grant: %s", data)
	}
}

func TestEnsureCanonicalAncestors_WormFoldersNoAutoOwn(t *testing.T) {
	root := t.TempDir()
	target := filepath.Join(root, "Proj", "archive", "ACME", "issued", "spec.pdf")

	_, err := EnsureCanonicalAncestors(root, target, "dc@mycompany.com", 0o755)
	if err != nil {
		t.Fatalf("ensure: %v", err)
	}

	if _, err := os.Stat(filepath.Join(root, "Proj", "archive", "ACME", "issued")); err != nil {
		t.Errorf("issued/ not created: %v", err)
	}
	if _, err := os.Stat(filepath.Join(root, "Proj", "archive", "ACME", "issued", ".zddc")); !os.IsNotExist(err) {
		t.Errorf("issued/ should NOT have auto-own .zddc (WORM); got err=%v", err)
	}
}

func TestEnsureCanonicalAncestors_NoPrincipalSkipsAutoOwn(t *testing.T) {
	root := t.TempDir()
	target := filepath.Join(root, "Proj", "working", "anon.md")

	_, err := EnsureCanonicalAncestors(root, target, "" /* no email */, 0o755)
	if err != nil {
		t.Fatalf("ensure: %v", err)
	}

	if _, err := os.Stat(filepath.Join(root, "Proj", "working")); err != nil {
		t.Errorf("working/ not created: %v", err)
	}
	if _, err := os.Stat(filepath.Join(root, "Proj", "working", ".zddc")); !os.IsNotExist(err) {
		t.Errorf("auto-own .zddc must not be written without principalEmail; got err=%v", err)
	}
}

func TestEnsureCanonicalAncestors_RejectsReviewing(t *testing.T) {
	root := t.TempDir()
	target := filepath.Join(root, "Proj", "reviewing", "x.md")
	_, err := EnsureCanonicalAncestors(root, target, "alice@x.com", 0o755)
	if err == nil {
		t.Errorf("expected error for write under reviewing/, got nil")
	}
	if _, err := os.Stat(filepath.Join(root, "Proj", "reviewing")); !os.IsNotExist(err) {
		t.Errorf("reviewing/ must NOT be created on disk; got err=%v", err)
	}
}

func TestEnsureCanonicalAncestors_RejectsTraversal(t *testing.T) {
	root := t.TempDir()
	other := t.TempDir()
	target := filepath.Join(other, "evil.md")
	_, err := EnsureCanonicalAncestors(root, target, "alice@x.com", 0o755)
	if err == nil {
		t.Errorf("expected error for target outside fsRoot")
	}
}
