package zddc

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

func TestWriteFileRoundTrip(t *testing.T) {
	root := t.TempDir()
	in := ZddcFile{
		Title: "Greenfield Substation",
		ACL: ACLRules{
			Allow: []string{"*@varasys.io"},
			Deny:  []string{"intern@varasys.io"},
		},
		Admins: []string{"alice@varasys.io"},
	}

	if err := WriteFile(root, in); err != nil {
		t.Fatalf("WriteFile: %v", err)
	}

	out, err := ParseFile(filepath.Join(root, ".zddc"))
	if err != nil {
		t.Fatalf("ParseFile: %v", err)
	}
	if out.Title != in.Title {
		t.Errorf("Title = %q, want %q", out.Title, in.Title)
	}
	if len(out.ACL.Allow) != 1 || out.ACL.Allow[0] != in.ACL.Allow[0] {
		t.Errorf("ACL.Allow = %v, want %v", out.ACL.Allow, in.ACL.Allow)
	}
	if len(out.Admins) != 1 || out.Admins[0] != "alice@varasys.io" {
		t.Errorf("Admins = %v, want [alice@varasys.io]", out.Admins)
	}
}

func TestWriteFileAtomicNoTempLeftBehind(t *testing.T) {
	root := t.TempDir()
	if err := WriteFile(root, ZddcFile{Title: "a"}); err != nil {
		t.Fatalf("WriteFile: %v", err)
	}

	entries, err := os.ReadDir(root)
	if err != nil {
		t.Fatalf("ReadDir: %v", err)
	}
	for _, e := range entries {
		if strings.HasSuffix(e.Name(), ".tmp") {
			t.Errorf("temp file left behind: %s", e.Name())
		}
	}
}

func TestWriteFileInvalidatesCache(t *testing.T) {
	root := t.TempDir()
	sub := filepath.Join(root, "project")
	if err := os.MkdirAll(sub, 0o755); err != nil {
		t.Fatalf("mkdir: %v", err)
	}

	// Prime the cache with an empty chain.
	if _, err := EffectivePolicy(root, sub); err != nil {
		t.Fatalf("prime cache: %v", err)
	}

	if err := WriteFile(sub, ZddcFile{
		ACL: ACLRules{Allow: []string{"alice@example.com"}},
	}); err != nil {
		t.Fatalf("WriteFile: %v", err)
	}

	// After write the cache must reflect the new content.
	chain, err := EffectivePolicy(root, sub)
	if err != nil {
		t.Fatalf("EffectivePolicy: %v", err)
	}
	if !chain.HasAnyFile {
		t.Fatal("HasAnyFile = false; cache not invalidated")
	}
	leaf := chain.Levels[len(chain.Levels)-1]
	if len(leaf.ACL.Allow) != 1 || leaf.ACL.Allow[0] != "alice@example.com" {
		t.Errorf("leaf allow = %v, want [alice@example.com]", leaf.ACL.Allow)
	}
}

func TestWriteFileOverwritePreservesOriginalOnFailure(t *testing.T) {
	// We can't easily simulate a rename failure portably, but we can at
	// least confirm that the happy-path overwrite produces the new
	// content (so the rename worked) and that the previous content is
	// gone (no merge or append).
	root := t.TempDir()
	if err := WriteFile(root, ZddcFile{Title: "first"}); err != nil {
		t.Fatalf("first write: %v", err)
	}
	if err := WriteFile(root, ZddcFile{Title: "second"}); err != nil {
		t.Fatalf("second write: %v", err)
	}
	out, err := ParseFile(filepath.Join(root, ".zddc"))
	if err != nil {
		t.Fatalf("parse: %v", err)
	}
	if out.Title != "second" {
		t.Errorf("Title = %q, want %q", out.Title, "second")
	}
}

func TestDeleteFile(t *testing.T) {
	root := t.TempDir()
	if err := WriteFile(root, ZddcFile{Title: "a"}); err != nil {
		t.Fatalf("WriteFile: %v", err)
	}
	if _, err := os.Stat(filepath.Join(root, ".zddc")); err != nil {
		t.Fatalf("file should exist before delete: %v", err)
	}

	// Prime the cache so we can verify invalidation post-delete.
	if _, err := EffectivePolicy(root, root); err != nil {
		t.Fatalf("prime: %v", err)
	}

	if err := DeleteFile(root); err != nil {
		t.Fatalf("DeleteFile: %v", err)
	}
	if _, err := os.Stat(filepath.Join(root, ".zddc")); !os.IsNotExist(err) {
		t.Errorf("file should be gone after delete: err=%v", err)
	}
	chain, err := EffectivePolicy(root, root)
	if err != nil {
		t.Fatalf("EffectivePolicy: %v", err)
	}
	if chain.HasAnyFile {
		t.Error("HasAnyFile should be false after delete; cache not invalidated")
	}
}

func TestDeleteFileMissing(t *testing.T) {
	root := t.TempDir()
	// No .zddc has been written; delete must be a no-op.
	if err := DeleteFile(root); err != nil {
		t.Errorf("DeleteFile on missing file = %v, want nil", err)
	}
}
