package acp

import (
	"encoding/json"
	"testing"
)

// TestAllowlistHit 覆盖白名单匹配的边界：空 toolName 一律不放行（含 "*"），
// 白名单中的空串条目不误匹配。
func TestAllowlistHit(t *testing.T) {
	cases := []struct {
		name      string
		allowlist []string
		toolName  string
		want      bool
	}{
		{"精确命中", []string{"safe.tool"}, "safe.tool", true},
		{"未命中", []string{"safe.tool"}, "other.tool", false},
		{"通配符放行可识别工具", []string{"*"}, "any.tool", true},
		{"通配符不放行空名", []string{"*"}, "", false},
		{"空名不匹配空串条目", []string{""}, "", false},
		{"空白名单", nil, "any.tool", false},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			if got := allowlistHit(c.allowlist, c.toolName); got != c.want {
				t.Fatalf("allowlistHit(%v, %q) = %v, want %v", c.allowlist, c.toolName, got, c.want)
			}
		})
	}
}

// TestExtractToolName_IgnoresAgentAssertedKindTitle 锁定安全约束：extractToolName
// 只信任真实工具名（name / toolName），绝不回退到 agent 自填的 kind / title。否则
// agent 可把 execute/shell 调用伪装成 kind='read' 或冒用白名单 title 绕过人工审批。
func TestExtractToolName_IgnoresAgentAssertedKindTitle(t *testing.T) {
	cases := []struct {
		name     string
		toolCall string
		want     string
	}{
		{"真实 name", `{"name":"shell.exec"}`, "shell.exec"},
		{"真实 toolName", `{"toolName":"shell.exec"}`, "shell.exec"},
		{"仅 kind 不取", `{"kind":"read"}`, ""},
		{"仅 title 不取", `{"title":"Read file"}`, ""},
		{"kind+title 都不取", `{"kind":"execute","title":"safe.tool"}`, ""},
		{"name 优先于 kind/title", `{"name":"real.tool","kind":"read","title":"x"}`, "real.tool"},
		{"空 toolCall", ``, ""},
		{"无可识别字段", `{"unknownField":1}`, ""},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			got := extractToolName(json.RawMessage(c.toolCall))
			if got != c.want {
				t.Fatalf("extractToolName(%q) = %q, want %q", c.toolCall, got, c.want)
			}
		})
	}
}

// TestAllowlist_DoesNotHonorAgentKind 端到端验证 bug #3：即使 allowlist 含 'read'
// 这类 kind 值，agent 把 execute 调用标成 kind='read' 也不会自动放行（extractToolName
// 返回空 → allowlistHit 为 false）。
func TestAllowlist_DoesNotHonorAgentKind(t *testing.T) {
	toolName := extractToolName(json.RawMessage(`{"kind":"read","title":"safe"}`))
	if toolName != "" {
		t.Fatalf("kind/title 不应被当作工具名, got %q", toolName)
	}
	if allowlistHit([]string{"read", "safe", "*"}, toolName) {
		t.Fatal("kind/title 伪装的调用不应命中白名单（含通配符）")
	}
}
