package main

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

// --- matchGlob tests ---

func TestMatchGlobExact(t *testing.T) {
	if !matchGlob("src/config.py", "src/config.py") {
		t.Error("exact match should succeed")
	}
	if matchGlob("src/config.py", "src/other.py") {
		t.Error("exact mismatch should fail")
	}
}

func TestMatchGlobPrefix(t *testing.T) {
	if !matchGlob("src/protected/*", "src/protected/config.py") {
		t.Error("prefix glob should match")
	}
	if matchGlob("src/protected/*", "src/other/config.py") {
		t.Error("prefix glob should not match different dir")
	}
}

func TestMatchGlobSuffix(t *testing.T) {
	if !matchGlob("*.pyc", "module.pyc") {
		t.Error("suffix glob should match")
	}
	if matchGlob("*.pyc", "module.py") {
		t.Error("suffix glob should not match wrong extension")
	}
}

// --- PreToolUse tests ---

func setupTestCache(t *testing.T, policies []Policy) {
	t.Helper()
	dir := t.TempDir()
	t.Setenv("HOME", dir)
	clewDir := filepath.Join(dir, ".clew")
	os.MkdirAll(clewDir, 0o755)
	data, _ := json.Marshal(policies)
	os.WriteFile(filepath.Join(clewDir, "policy-cache.json"), data, 0o644)
}

func TestPreToolUseAllowSafeCommand(t *testing.T) {
	setupTestCache(t, []Policy{
		{ID: "ban-1", Type: "banned_import", Pattern: "dangerous_module", Severity: "block", Message: "banned"},
	})
	input := `{"tool_name":"Bash","tool_input":{"command":"git status"}}`
	code := handlePreToolUse([]byte(input))
	if code != 0 {
		t.Errorf("expected exit 0 for safe command, got %d", code)
	}
}

func TestPreToolUseDenyBannedImport(t *testing.T) {
	setupTestCache(t, []Policy{
		{ID: "ban-1", Type: "banned_import", Pattern: "dangerous_module", Severity: "block", Message: "banned"},
	})
	// Capture stdout
	old := os.Stdout
	r, w, _ := os.Pipe()
	os.Stdout = w

	input := `{"tool_name":"Bash","tool_input":{"command":"import dangerous_module"}}`
	code := handlePreToolUse([]byte(input))

	w.Close()
	os.Stdout = old

	if code != 0 {
		t.Errorf("expected exit 0 (deny via JSON), got %d", code)
	}

	var buf [4096]byte
	n, _ := r.Read(buf[:])
	output := string(buf[:n])

	var out HookOutput
	if err := json.Unmarshal([]byte(output), &out); err != nil {
		t.Fatalf("invalid JSON output: %s", output)
	}
	if out.HookSpecificOutput.PermissionDecision != "deny" {
		t.Errorf("expected deny, got %s", out.HookSpecificOutput.PermissionDecision)
	}
}

func TestPreToolUseDenyProtectedWrite(t *testing.T) {
	setupTestCache(t, []Policy{
		{ID: "pw-1", Type: "protected_write", Pattern: "src/protected/*", Severity: "block", Message: "protected"},
	})
	old := os.Stdout
	r, w, _ := os.Pipe()
	os.Stdout = w

	input := `{"tool_name":"Write","tool_input":{"file_path":"src/protected/config.py","content":"x=1"}}`
	code := handlePreToolUse([]byte(input))

	w.Close()
	os.Stdout = old

	if code != 0 {
		t.Errorf("expected exit 0, got %d", code)
	}

	var buf [4096]byte
	n, _ := r.Read(buf[:])
	var out HookOutput
	json.Unmarshal(buf[:n], &out)
	if out.HookSpecificOutput.PermissionDecision != "deny" {
		t.Errorf("expected deny for protected write")
	}
}

func TestPreToolUseSkipReadTool(t *testing.T) {
	setupTestCache(t, []Policy{
		{ID: "ban-1", Type: "banned_import", Pattern: "dangerous", Severity: "block", Message: "banned"},
	})
	input := `{"tool_name":"Read","tool_input":{"file_path":"src/foo.py"}}`
	code := handlePreToolUse([]byte(input))
	if code != 0 {
		t.Errorf("expected exit 0 for Read (no checks), got %d", code)
	}
}

func TestPreToolUseNoCache(t *testing.T) {
	t.Setenv("HOME", t.TempDir()) // no cache file
	input := `{"tool_name":"Bash","tool_input":{"command":"import dangerous"}}`
	code := handlePreToolUse([]byte(input))
	if code != 0 {
		t.Errorf("expected exit 0 (fail-open with no cache), got %d", code)
	}
}

func TestPreToolUseWarnSeverityNotBlocked(t *testing.T) {
	setupTestCache(t, []Policy{
		{ID: "ban-1", Type: "banned_import", Pattern: "dangerous", Severity: "warn", Message: "just a warning"},
	})
	input := `{"tool_name":"Bash","tool_input":{"command":"import dangerous"}}`
	code := handlePreToolUse([]byte(input))
	if code != 0 {
		t.Errorf("expected exit 0 (warn severity should not block), got %d", code)
	}
}

// --- PostToolUse audit tests ---

func TestPostToolUseWritesAuditLog(t *testing.T) {
	dir := t.TempDir()
	t.Setenv("HOME", dir)
	os.MkdirAll(filepath.Join(dir, ".clew"), 0o755)

	input := `{"tool_name":"Write","tool_input":{"file_path":"src/foo.py","content":"x=1"}}`
	code := handlePostToolUse([]byte(input))
	if code != 0 {
		t.Errorf("expected exit 0, got %d", code)
	}

	logPath := filepath.Join(dir, ".clew", "audit.jsonl")
	data, err := os.ReadFile(logPath)
	if err != nil {
		t.Fatalf("audit log not created: %v", err)
	}

	var entry AuditEntry
	if err := json.Unmarshal(data, &entry); err != nil {
		t.Fatalf("invalid audit entry: %v", err)
	}
	if entry.ToolName != "Write" {
		t.Errorf("expected tool_name Write, got %s", entry.ToolName)
	}
	if entry.FilePath != "src/foo.py" {
		t.Errorf("expected file_path src/foo.py, got %s", entry.FilePath)
	}
	if entry.Decision != "allow" {
		t.Errorf("expected decision allow, got %s", entry.Decision)
	}
}

func TestPostToolUseAppendsMultipleEntries(t *testing.T) {
	dir := t.TempDir()
	t.Setenv("HOME", dir)
	os.MkdirAll(filepath.Join(dir, ".clew"), 0o755)

	handlePostToolUse([]byte(`{"tool_name":"Bash","tool_input":{"command":"ls"}}`))
	handlePostToolUse([]byte(`{"tool_name":"Read","tool_input":{"file_path":"x.py"}}`))

	logPath := filepath.Join(dir, ".clew", "audit.jsonl")
	data, err := os.ReadFile(logPath)
	if err != nil {
		t.Fatalf("audit log not created: %v", err)
	}

	lines := strings.Split(strings.TrimSpace(string(data)), "\n")
	if len(lines) != 2 {
		t.Errorf("expected 2 audit entries, got %d", len(lines))
	}
}

// --- buildPolicySummary tests ---

func TestBuildPolicySummaryEmpty(t *testing.T) {
	s := buildPolicySummary(nil)
	if s != "Clew policy cache loaded: no active policies." {
		t.Errorf("unexpected summary: %s", s)
	}
}

func TestBuildPolicySummaryCounts(t *testing.T) {
	policies := []Policy{
		{ID: "1", Type: "banned_import", Pattern: "x", Severity: "block", Message: "m1"},
		{ID: "2", Type: "protected_write", Pattern: "y", Severity: "warn", Message: "m2"},
		{ID: "3", Type: "unguarded_path", Pattern: "z", Severity: "audit", Message: "m3"},
	}
	s := buildPolicySummary(policies)
	if s == "" {
		t.Error("summary should not be empty")
	}
	expected := "3 policies (1 block, 1 warn, 1 audit)"
	if !contains(s, expected) {
		t.Errorf("expected %q in summary, got: %s", expected, s)
	}
}

func contains(s, sub string) bool {
	return len(s) >= len(sub) && (s == sub || len(s) > 0 && containsHelper(s, sub))
}

func containsHelper(s, sub string) bool {
	for i := 0; i <= len(s)-len(sub); i++ {
		if s[i:i+len(sub)] == sub {
			return true
		}
	}
	return false
}
