package tools

// Tool is one entry of the MCP tools/list registry: an LLM-facing
// facade over one or more backend GET endpoints (plan §4.3).
type Tool struct {
	// Name is the MCP tool name (snake_case).
	Name string
	// Description tells the LLM what the tool does and when to use it.
	Description string
	// InputSchema is the JSON Schema of the tool arguments.
	InputSchema *Schema
	// Endpoints are the backend operations backing this tool, primary
	// endpoint first. Path includes the /api/v1 prefix and matches the
	// embedded swagger snapshot verbatim.
	Endpoints []EndpointRef
}

// EndpointRef binds a tool to one backend endpoint.
type EndpointRef struct {
	// Method is the HTTP method, matching the swagger operation key:
	// "get" for the read-only tools, "post"/"put" for the write tools.
	Method string
	// Path is the backend path as declared in swagger (with the /api/v1
	// prefix and {param} placeholders).
	Path string
	// ParamMap maps tool argument name → backend query/path parameter
	// name. Only parameters this endpoint actually declares are mapped;
	// dispatch-only arguments (e.g. list_vuln_lookup_options' kind) are
	// intentionally absent.
	ParamMap map[string]string
	// BodyMap maps tool argument name → JSON request body field name for
	// write endpoints (post/put). Mapped arguments are carried in the
	// JSON body with their native JSON types instead of the query
	// string. It must be empty on GET endpoints and disjoint from
	// ParamMap (one argument goes to exactly one place). Arguments
	// absent from ParamMap, BodyMap and the dispatch layer are dropped,
	// same as before.
	BodyMap map[string]string
}

// paramMap builds an identity ParamMap (tool argument name == backend
// parameter name) for the given names.
func paramMap(names ...string) map[string]string {
	m := make(map[string]string, len(names))
	for _, n := range names {
		m[n] = n
	}
	return m
}

// bodyMap builds an identity BodyMap (tool argument name == JSON body
// field name) for the given names.
func bodyMap(names ...string) map[string]string {
	m := make(map[string]string, len(names))
	for _, n := range names {
		m[n] = n
	}
	return m
}

// Registry returns the MCP tools: the 14 read-only tools from plan
// §4.3 plus the five write tools from P3-T4/P3-T5 (vuln_write.go and
// vuln_upload.go, appended at the end) and the whoami identity tool
// (whoami.go, registered last). The vulnerability
// viewers/candidates endpoint is deliberately not exposed (授权管理类查询，非安全分析意图).
//
// Merge conventions for multi-endpoint tools: the InputSchema is the
// union of the endpoints' parameters; on conflicts the primary
// (first) endpoint wins. Tools whose detail sub-endpoints take only a
// path id use that id as the mode switch: when set, the caller
// (P4-T5) queries the detail/comments/events endpoints instead of the
// list endpoint. Parameterless auxiliary endpoints (stats, services,
// environments, registrars, config/active) are reached through the
// dispatch-only "view" argument (see per-tool comments); like the vuln
// tools' "kind" it maps no backend parameter and never reaches the
// query string.
func Registry() []Tool {
	tools := []Tool{
		{
			Name: "search_assets",
			Description: "搜索资产清单（主机/IP/域名/服务等），支持按类型、责任人、环境、云账号、云厂商、服务、关键词过滤；" +
				"带 region/exposure/asset_service 条件时查 IP 资产维度；view=services/environments 时改查服务名/环境枚举值（忽略其余参数与分页）。" +
				"典型场景：“某负责人名下有哪些资产”“生产环境公网暴露的 IP 有哪些”。",
			// 合并 assets + ip-assets + assets/services + assets/environments。
			// 主端点 /caasm/assets；region/exposure/asset_service 仅 ip-assets 声明。
			// 无命名冲突（q/provider/cloud_account_id 两端点同义同型）。
			// assets/services 与 assets/environments 无参数，由 view 调度。
			InputSchema: objectSchema(paginated(map[string]Property{
				"q":                 strProp("关键词搜索"),
				"type":              strProp("资产类型（AssetType）"),
				"owner":             strProp("责任人邮箱"),
				"owner_employee_id": intProp("责任人员工 ID"),
				"cloud_account_id":  intProp("云账号 ID"),
				"environment":       strProp("环境（可选值用 view=environments 查询）"),
				"status":            strProp("资产状态（AssetStatus）"),
				"criticality":       strProp("重要性"),
				"provider":          strProp("云厂商（CloudProvider）"),
				"service":           strProp("服务名（可选值用 view=services 查询）"),
				"asset_service":     strProp("资产服务名（仅 IP 资产维度生效）"),
				"region":            strProp("区域（仅 IP 资产维度生效）"),
				"exposure":          {Type: "string", Description: "暴露面（仅 IP 资产维度生效）", Enum: []string{"public", "private"}},
				"view":              {Type: "string", Description: "辅助视图：services=服务名枚举 / environments=环境枚举", Enum: []string{"services", "environments"}},
			})),
			Endpoints: []EndpointRef{
				{Method: "get", Path: "/api/v1/caasm/assets", ParamMap: paramMap(
					"page", "page_size", "q", "type", "owner", "owner_employee_id",
					"cloud_account_id", "environment", "status", "criticality", "provider", "service")},
				{Method: "get", Path: "/api/v1/caasm/ip-assets", ParamMap: paramMap(
					"page", "page_size", "q", "provider", "cloud_account_id", "asset_service", "region", "exposure")},
				{Method: "get", Path: "/api/v1/caasm/assets/services"},
				{Method: "get", Path: "/api/v1/caasm/assets/environments"},
			},
		},
		{
			Name:        "get_asset",
			Description: "按资产 ID 获取单个资产的完整详情与标签。典型场景：search_assets 命中候选后，深入查看某个资产的配置、归属与打标。",
			// 合并 assets/{id} + assets/{id}/tags；两个端点共用 id，调用方两次请求后合并返回。
			InputSchema: objectSchema(map[string]Property{
				"id": intProp("资产 ID（来自 search_assets 结果）"),
			}, "id"),
			Endpoints: []EndpointRef{
				{Method: "get", Path: "/api/v1/caasm/assets/{id}", ParamMap: paramMap("id")},
				{Method: "get", Path: "/api/v1/caasm/assets/{id}/tags", ParamMap: paramMap("id")},
			},
		},
		{
			Name: "list_ports",
			Description: "查询端口暴露面：按端口号/协议/状态/暴露面/服务过滤分页列出端口；传入 id 时返回单个端口详情；" +
				"view=stats 返回端口统计概览，view=by_asset 按资产维度搜索（参数与主列表一致）。view 与 id 互斥。" +
				"典型场景：“哪些资产开放了 3306”“公网暴露了哪些端口”。",
			// 合并 ports + ports/{id} + ports/stats + assets/ports/search。
			// /assets/ports/search 与主端点 /ports 参数完全一致（语义别名），无冲突。
			// ports/stats 无参数、assets/ports/search 为资产维度别名，均由 view 调度（与 id 互斥）。
			InputSchema: objectSchema(paginated(map[string]Property{
				"id":            intProp("端口 ID：传入时返回该端口详情（忽略其余过滤参数与分页）"),
				"port":          intProp("端口号"),
				"protocol":      strProp("协议（tcp/udp）"),
				"status":        strProp("端口状态"),
				"exposure":      strProp("暴露面"),
				"service":       strProp("服务名"),
				"asset_service": strProp("资产服务名"),
				"view":          {Type: "string", Description: "辅助视图：stats=端口统计（忽略其余参数）/ by_asset=资产维度搜索（过滤参数同主列表）", Enum: []string{"stats", "by_asset"}},
			})),
			Endpoints: []EndpointRef{
				{Method: "get", Path: "/api/v1/caasm/ports", ParamMap: paramMap(
					"page", "page_size", "port", "protocol", "status", "exposure", "service", "asset_service")},
				{Method: "get", Path: "/api/v1/caasm/ports/{id}", ParamMap: paramMap("id")},
				{Method: "get", Path: "/api/v1/caasm/ports/stats"},
				{Method: "get", Path: "/api/v1/caasm/assets/ports/search", ParamMap: paramMap(
					"page", "page_size", "port", "protocol", "status", "exposure", "service", "asset_service")},
			},
		},
		{
			Name: "list_high_risk_ports",
			Description: "列出全网高危开放端口（后端一次返回完整数组，未分页，故无 page/page_size）。" +
				"典型场景：快速盘点“当前有哪些危险端口（如 3389/6379）开放”。",
			// 单端点 ports/high-risk，swagger 未声明任何参数。
			InputSchema: objectSchema(nil),
			Endpoints: []EndpointRef{
				{Method: "get", Path: "/api/v1/caasm/ports/high-risk"},
			},
		},
		{
			Name: "list_dns_records",
			Description: "查询 DNS 记录列表，支持按记录类型、域名、记录值、是否有告警等过滤；传入 id 时返回该记录的评论列表。" +
				"典型场景：“某域名下有哪些解析记录”“哪些 DNS 记录有告警”。",
			// 合并 dns-records + dns-records/{id}/comments；id 为模式开关（评论）。
			InputSchema: objectSchema(paginated(map[string]Property{
				"id":                  intProp("DNS 记录 ID：传入时返回该记录的评论（忽略其余过滤参数与分页）"),
				"q":                   strProp("关键词"),
				"type":                strProp("DNS 记录类型（A/CNAME 等）"),
				"owner_department_id": strProp("责任人部门 ID"),
				"domain_id":           intProp("域名 ID"),
				"domain_name":         strProp("域名"),
				"value":               strProp("记录值模糊匹配"),
				"has_alert":           boolProp("是否有告警"),
			})),
			Endpoints: []EndpointRef{
				{Method: "get", Path: "/api/v1/caasm/dns-records", ParamMap: paramMap(
					"page", "page_size", "q", "type", "owner_department_id", "domain_id", "domain_name", "value", "has_alert")},
				{Method: "get", Path: "/api/v1/caasm/dns-records/{id}/comments", ParamMap: paramMap("id")},
			},
		},
		{
			Name: "list_domains",
			Description: "查询域名资产列表，支持按状态、注册商、Zone 类型、是否自动续费等过滤；" +
				"view=stats 返回域名统计，view=registrars 返回注册商枚举（传入时忽略其余参数与分页）。" +
				"典型场景：“未开启自动续费的域名有哪些”“某注册商下的域名清单”。",
			// 合并 domains + domains/stats + domains/registrars；后两者无参数，由 view 调度。
			InputSchema: objectSchema(paginated(map[string]Property{
				"q":            strProp("关键词"),
				"environment":  strProp("环境"),
				"status":       strProp("域名状态"),
				"zone_type":    strProp("Zone 类型"),
				"domain_class": strProp("域名分类"),
				"registrar":    strProp("注册商（可选值用 view=registrars 查询）"),
				"provider":     strProp("云厂商（CloudProvider）"),
				"forwarding":   boolProp("是否存在转发"),
				"peering":      boolProp("是否存在 Peering"),
				"auto_renew":   boolProp("是否自动续费"),
				"view":         {Type: "string", Description: "辅助视图：stats=域名统计 / registrars=注册商枚举", Enum: []string{"stats", "registrars"}},
			})),
			Endpoints: []EndpointRef{
				{Method: "get", Path: "/api/v1/caasm/domains", ParamMap: paramMap(
					"page", "page_size", "q", "environment", "status", "zone_type", "domain_class",
					"registrar", "provider", "forwarding", "peering", "auto_renew")},
				{Method: "get", Path: "/api/v1/caasm/domains/stats"},
				{Method: "get", Path: "/api/v1/caasm/domains/registrars"},
			},
		},
		{
			Name: "get_relationship_graph",
			Description: "获取资产关系图（资产/域名/IP/端口之间的节点与边）。典型场景：分析某资产或全局的关联拓扑与影响面。" +
				"注意：图数据量可能较大，结果按需摘要。",
			// 单端点 relationships/graph，swagger 未声明任何参数。
			InputSchema: objectSchema(nil),
			Endpoints: []EndpointRef{
				{Method: "get", Path: "/api/v1/caasm/relationships/graph"},
			},
		},
		{
			Name: "list_cloud_accounts",
			Description: "查询已接入的云账号列表（可按云厂商/状态/关键词过滤）；传入 id 时返回该账号详情及其同步任务列表。" +
				"典型场景：“接入了哪些云账号”“某个云账号的同步是否正常”。",
			// 合并 cloud-accounts + cloud-accounts/{id} + cloud-accounts/{id}/jobs。
			// page/page_size 同时适用于账号列表与 jobs 子列表（同义同型，无冲突）。
			InputSchema: objectSchema(paginated(map[string]Property{
				"id":       intProp("云账号 ID：传入时返回该账号详情及其同步任务（忽略 q/provider/status）"),
				"q":        strProp("关键词"),
				"provider": strProp("云厂商（CloudProvider）"),
				"status":   strProp("云账号状态（CloudAccountStatus）"),
			})),
			Endpoints: []EndpointRef{
				{Method: "get", Path: "/api/v1/caasm/cloud-accounts", ParamMap: paramMap(
					"page", "page_size", "q", "provider", "status")},
				{Method: "get", Path: "/api/v1/caasm/cloud-accounts/{id}", ParamMap: paramMap("id")},
				{Method: "get", Path: "/api/v1/caasm/cloud-accounts/{id}/jobs", ParamMap: paramMap(
					"id", "page", "page_size")},
			},
		},
		{
			Name: "list_sync_jobs",
			Description: "查询资产同步任务列表（按类型/状态/云账号/执行模式过滤）；传入 id 返回任务详情，" +
				"配合 limit/before_id/after_id 翻页读取任务事件流。典型场景：“最近一次同步是否成功”“失败任务报了哪些错误事件”。",
			// 合并 sync-jobs + sync-jobs/{id} + sync-jobs/{id}/events。
			// limit/before_id/after_id 仅 events 端点声明（需与 id 同传）；before_id 与 after_id 互斥。
			InputSchema: objectSchema(paginated(map[string]Property{
				"id":               intProp("同步任务 ID：传入时返回任务详情；与 limit/before_id/after_id 同传时读事件流"),
				"job_type":         strProp("任务类型"),
				"status":           strProp("任务状态（SyncJobStatus）"),
				"cloud_account_id": intProp("云账号 ID"),
				"execution_mode":   strProp("执行模式"),
				"limit":            intProp("事件流返回条数上限（仅事件流模式）"),
				"before_id":        intProp("仅返回该事件 ID 之前的事件（与 after_id 互斥）"),
				"after_id":         intProp("仅返回该事件 ID 之后的事件（与 before_id 互斥）"),
			})),
			Endpoints: []EndpointRef{
				{Method: "get", Path: "/api/v1/caasm/sync-jobs", ParamMap: paramMap(
					"page", "page_size", "job_type", "status", "cloud_account_id", "execution_mode")},
				{Method: "get", Path: "/api/v1/caasm/sync-jobs/{id}", ParamMap: paramMap("id")},
				{Method: "get", Path: "/api/v1/caasm/sync-jobs/{id}/events", ParamMap: paramMap(
					"id", "limit", "before_id", "after_id")},
			},
		},
		{
			Name: "list_api_route_assets",
			Description: "查询发现的 API 路由资产，支持按服务、命名空间、插件、路由类型、是否外部路由等过滤；" +
				"view=config 返回生效中的发现配置（忽略其余参数与分页）。" +
				"典型场景：“某服务对外暴露了哪些 API 路由”“某插件采集到哪些路由”。",
			// 合并 api-assets + api-assets/config/active；后者无参数（生效中的发现配置），
			// 由 view 调度。
			InputSchema: objectSchema(paginated(map[string]Property{
				"q":            strProp("关键词"),
				"environment":  strProp("环境"),
				"domain":       strProp("域名"),
				"namespace":    strProp("命名空间"),
				"service_name": strProp("服务名"),
				"plugin":       strProp("插件名"),
				"route_kind":   strProp("路由类型"),
				"external":     boolProp("是否外部路由"),
				"view":         {Type: "string", Description: "辅助视图：config=生效中的 API 资产发现配置", Enum: []string{"config"}},
			})),
			Endpoints: []EndpointRef{
				{Method: "get", Path: "/api/v1/caasm/api-assets", ParamMap: paramMap(
					"page", "page_size", "q", "environment", "domain", "namespace", "service_name",
					"plugin", "route_kind", "external")},
				{Method: "get", Path: "/api/v1/caasm/api-assets/config/active"},
			},
		},
		{
			Name: "search_vulnerabilities",
			Description: "按严重程度、状态、应用、标签、发现渠道/阶段、网络环境、依赖、提交时间等条件搜索漏洞，支持排序与分页。" +
				"查“我的”漏洞（我提交的/指派给我的）前先用 whoami 解析当前身份，再用 submitter_user_id=user_id 或 owner_employee_id=employee_id 过滤。" +
				"典型场景：“某应用有哪些未修复的严重漏洞”“本周新提交的漏洞清单”。",
			// 单端点 /vulnerabilities，参数全量对齐 swagger。
			InputSchema: objectSchema(paginated(map[string]Property{
				"status":             strProp("状态多选（逗号分隔）"),
				"severity":           strProp("严重程度多选（逗号分隔）"),
				"application":        strProp("应用"),
				"discovery_phase":    strProp("发现阶段"),
				"discovery_channel":  strProp("发现渠道"),
				"network_env":        strProp("网络环境"),
				"search":             strProp("关键词"),
				"tags":               strProp("标签多选（逗号分隔）"),
				"dependency_name":    strProp("依赖名"),
				"dependency_version": strProp("依赖版本"),
				"owner_employee_id":  intProp("责任人员工 ID"),
				"submitter_user_id":  intProp("提交人用户 ID"),
				"sort_by":            strProp("排序字段"),
				"sort_order":         {Type: "string", Description: "排序方向", Enum: []string{"asc", "desc"}},
				"batch_group_id":     intProp("批次组 ID"),
				"batch_scope":        {Type: "string", Description: "批次范围", Enum: []string{"batched", "single"}},
				"has_poc":            boolProp("是否有 POC 验证"),
				"is_overdue":         boolProp("是否超期"),
				"is_third_party":     boolProp("是否第三方漏洞"),
				"submitted_from":     strProp("提交时间起（RFC3339）"),
				"submitted_to":       strProp("提交时间止（RFC3339）"),
			})),
			Endpoints: []EndpointRef{
				{Method: "get", Path: "/api/v1/vulnerabilities", ParamMap: paramMap(
					"page", "page_size", "status", "severity", "application", "discovery_phase",
					"discovery_channel", "network_env", "search", "tags", "dependency_name",
					"dependency_version", "owner_employee_id", "submitter_user_id", "sort_by",
					"sort_order", "batch_group_id", "batch_scope", "has_poc", "is_overdue",
					"is_third_party", "submitted_from", "submitted_to")},
			},
		},
		{
			Name: "get_vulnerability",
			Description: "按 public_id 获取单个漏洞的完整档案：详情 + 生命周期事件流 + 评论。" +
				"典型场景：search_vulnerabilities 命中后，跟进某个漏洞的来龙去脉与讨论记录。",
			// 合并 vulnerabilities/{public_id} + .../events + .../comments；
			// 三个端点共用 public_id，调用方分别请求后合并返回。
			InputSchema: objectSchema(map[string]Property{
				"public_id": strProp("漏洞 Public ID（字符串，来自 search_vulnerabilities 结果，非数字 ID）"),
			}, "public_id"),
			Endpoints: []EndpointRef{
				{Method: "get", Path: "/api/v1/vulnerabilities/{public_id}", ParamMap: paramMap("public_id")},
				{Method: "get", Path: "/api/v1/vulnerabilities/{public_id}/events", ParamMap: paramMap("public_id")},
				{Method: "get", Path: "/api/v1/vulnerabilities/{public_id}/comments", ParamMap: paramMap("public_id")},
			},
		},
		{
			Name: "list_vuln_lookup_options",
			Description: "查询漏洞字典选项，kind 取值 tags（标签）/types（类型）/sources（来源）/channels（发现渠道）。" +
				"典型场景：调用 search_vulnerabilities 前先查可用的标签、类型、来源等过滤值。",
			// 合并 vulnerability-tags/types/sources/discovery-channels 四个字典端点。
			// kind 为调度参数，不映射任何后端参数。
			// category 语义随 kind 变化（tags=标签分类 / types=类型分类），命名以主端点 tags 为准；
			// severity 仅 types 声明，其余 kind 下忽略。
			InputSchema: objectSchema(paginated(map[string]Property{
				"kind":        {Type: "string", Description: "字典类别：tags=标签 / types=类型 / sources=来源 / channels=发现渠道", Enum: []string{"tags", "types", "sources", "channels"}},
				"q":           strProp("关键词"),
				"category":    strProp("分类（tags=标签分类，types=类型分类；sources/channels 下忽略）"),
				"severity":    strProp("严重程度（仅 kind=types 生效）"),
				"system_only": boolProp("仅系统内置项"),
			}), "kind"),
			Endpoints: []EndpointRef{
				{Method: "get", Path: "/api/v1/vulnerability-tags", ParamMap: paramMap(
					"page", "page_size", "q", "category", "system_only")},
				{Method: "get", Path: "/api/v1/vulnerability-types", ParamMap: paramMap(
					"page", "page_size", "q", "category", "severity", "system_only")},
				{Method: "get", Path: "/api/v1/vulnerability-sources", ParamMap: paramMap(
					"page", "page_size", "q", "system_only")},
				{Method: "get", Path: "/api/v1/vulnerability-discovery-channels", ParamMap: paramMap(
					"page", "page_size", "q", "system_only")},
			},
		},
		{
			Name: "get_vuln_tags_stats",
			Description: "获取漏洞标签统计（各标签的漏洞数量聚合，无参数）。" +
				"典型场景：“哪类标签的漏洞占比最高”的标签维度分布概览。",
			// 单端点 vulnerability-tags/stats，swagger 未声明任何参数。
			InputSchema: objectSchema(nil),
			Endpoints: []EndpointRef{
				{Method: "get", Path: "/api/v1/vulnerability-tags/stats"},
			},
		},
	}
	// P3-T4: the four JSON write tools live in vuln_write.go; appended
	// here so every registry-level check (swagger conformance, BodyMap
	// shape, schema sanity) covers them too.
	// P3-T5: upload_vulnerability_attachment (vuln_upload.go) is
	// registered last; dispatch is the custom three-step orchestration
	// in vuln.go, its two Endpoints entries only record the contract
	// paths for the registry-level swagger checks.
	// whoami (whoami.go) closes the list: a no-arg identity tool whose
	// backend endpoint is deliberately outside the swagger snapshot
	// (see swaggerExemptPaths in registry_test.go).
	tools = append(tools, vulnWriteTools()...)
	return append(tools, vulnUploadTool(), whoamiTool())
}

// ListEntry is one tool in the MCP tools/list result.
type ListEntry struct {
	Name        string  `json:"name"`
	Description string  `json:"description"`
	InputSchema *Schema `json:"inputSchema"`
}

// ListResult mirrors the MCP tools/list response payload. Protocol
// wiring (JSON-RPC envelope) lands in P4-T6; this is the plain struct.
type ListResult struct {
	Tools []ListEntry `json:"tools"`
}

// List returns the MCP-style tools/list result for the registry.
func List() ListResult {
	reg := Registry()
	out := ListResult{Tools: make([]ListEntry, 0, len(reg))}
	for _, t := range reg {
		out.Tools = append(out.Tools, ListEntry{
			Name:        t.Name,
			Description: t.Description,
			InputSchema: t.InputSchema,
		})
	}
	return out
}
