package cmd

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

func setupGitRepo(t *testing.T) string {
	t.Helper()
	tmp := t.TempDir()
	t.Chdir(tmp)
	if err := os.MkdirAll(filepath.Join(tmp, ".git", "hooks"), 0o755); err != nil {
		t.Fatalf("mkdir .git/hooks: %v", err)
	}
	// Reset force flag between tests.
	hookForce = false
	return tmp
}

func TestHookInstall_FreshRepo(t *testing.T) {
	tmp := setupGitRepo(t)
	if err := runHookInstall(hookInstallCmd, nil); err != nil {
		t.Fatalf("install: %v", err)
	}
	p := filepath.Join(tmp, ".git", "hooks", "pre-commit")
	info, err := os.Stat(p)
	if err != nil {
		t.Fatalf("stat: %v", err)
	}
	if info.Mode().Perm() != 0o755 {
		t.Errorf("mode = %v, want 0o755", info.Mode().Perm())
	}
	data, _ := os.ReadFile(p)
	if !strings.Contains(string(data), hookMarker) {
		t.Errorf("hook missing marker %q", hookMarker)
	}
}

func TestHookInstall_NotAGitRepo(t *testing.T) {
	tmp := t.TempDir()
	t.Chdir(tmp)
	hookForce = false
	err := runHookInstall(hookInstallCmd, nil)
	if err == nil || !strings.Contains(err.Error(), "not a git repository") {
		t.Fatalf("want not-a-git-repo error, got %v", err)
	}
}

func TestHookInstall_ExistingNonKeyhunterRefuses(t *testing.T) {
	tmp := setupGitRepo(t)
	p := filepath.Join(tmp, ".git", "hooks", "pre-commit")
	original := "# my hook\n"
	if err := os.WriteFile(p, []byte(original), 0o755); err != nil {
		t.Fatal(err)
	}
	err := runHookInstall(hookInstallCmd, nil)
	if err == nil {
		t.Fatal("expected error, got nil")
	}
	data, _ := os.ReadFile(p)
	if string(data) != original {
		t.Errorf("file changed; got %q", string(data))
	}
}

func TestHookInstall_ForceBackupsExisting(t *testing.T) {
	tmp := setupGitRepo(t)
	p := filepath.Join(tmp, ".git", "hooks", "pre-commit")
	original := "# my hook\n"
	if err := os.WriteFile(p, []byte(original), 0o755); err != nil {
		t.Fatal(err)
	}
	hookForce = true
	defer func() { hookForce = false }()
	if err := runHookInstall(hookInstallCmd, nil); err != nil {
		t.Fatalf("install --force: %v", err)
	}
	// backup file should exist
	entries, _ := os.ReadDir(filepath.Join(tmp, ".git", "hooks"))
	foundBackup := false
	for _, e := range entries {
		if strings.HasPrefix(e.Name(), "pre-commit.bak.") {
			foundBackup = true
			data, _ := os.ReadFile(filepath.Join(tmp, ".git", "hooks", e.Name()))
			if string(data) != original {
				t.Errorf("backup content mismatch: %q", string(data))
			}
		}
	}
	if !foundBackup {
		t.Error("no backup file found")
	}
	data, _ := os.ReadFile(p)
	if !strings.Contains(string(data), hookMarker) {
		t.Error("new hook missing marker")
	}
}

func TestHookInstall_ExistingKeyhunterOverwrites(t *testing.T) {
	tmp := setupGitRepo(t)
	p := filepath.Join(tmp, ".git", "hooks", "pre-commit")
	stale := "#!/bin/sh\n# " + hookMarker + " stale\n"
	if err := os.WriteFile(p, []byte(stale), 0o755); err != nil {
		t.Fatal(err)
	}
	if err := runHookInstall(hookInstallCmd, nil); err != nil {
		t.Fatalf("install: %v", err)
	}
	data, _ := os.ReadFile(p)
	if string(data) == stale {
		t.Error("hook not updated")
	}
	if !strings.Contains(string(data), hookMarker) {
		t.Error("updated hook missing marker")
	}
}

func TestHookUninstall_RemovesKeyhunter(t *testing.T) {
	tmp := setupGitRepo(t)
	if err := runHookInstall(hookInstallCmd, nil); err != nil {
		t.Fatal(err)
	}
	if err := runHookUninstall(hookUninstallCmd, nil); err != nil {
		t.Fatalf("uninstall: %v", err)
	}
	p := filepath.Join(tmp, ".git", "hooks", "pre-commit")
	if _, err := os.Stat(p); !os.IsNotExist(err) {
		t.Errorf("file still exists: %v", err)
	}
}

func TestHookUninstall_RefusesForeign(t *testing.T) {
	tmp := setupGitRepo(t)
	p := filepath.Join(tmp, ".git", "hooks", "pre-commit")
	original := "#!/bin/sh\n# foreign\n"
	if err := os.WriteFile(p, []byte(original), 0o755); err != nil {
		t.Fatal(err)
	}
	err := runHookUninstall(hookUninstallCmd, nil)
	if err == nil {
		t.Fatal("expected error")
	}
	data, _ := os.ReadFile(p)
	if string(data) != original {
		t.Error("file was modified")
	}
}

func TestHookUninstall_Force(t *testing.T) {
	tmp := setupGitRepo(t)
	p := filepath.Join(tmp, ".git", "hooks", "pre-commit")
	if err := os.WriteFile(p, []byte("# foreign\n"), 0o755); err != nil {
		t.Fatal(err)
	}
	hookForce = true
	defer func() { hookForce = false }()
	if err := runHookUninstall(hookUninstallCmd, nil); err != nil {
		t.Fatalf("uninstall --force: %v", err)
	}
	if _, err := os.Stat(p); !os.IsNotExist(err) {
		t.Error("file still exists")
	}
}

func TestHookUninstall_Missing(t *testing.T) {
	setupGitRepo(t)
	if err := runHookUninstall(hookUninstallCmd, nil); err != nil {
		t.Fatalf("uninstall missing: %v", err)
	}
}

func TestHookScript_ContainsRequired(t *testing.T) {
	if !strings.Contains(hookScript, "keyhunter scan") {
		t.Error("embedded script missing 'keyhunter scan'")
	}
	if !strings.Contains(hookScript, "git diff --cached") {
		t.Error("embedded script missing 'git diff --cached'")
	}
	if !strings.Contains(hookScript, hookMarker) {
		t.Error("embedded script missing marker")
	}
}
