package tools

import (
	"context"
	"errors"
	"fmt"

	"security-center-mcp/internal/client"
)

// This file implements tools/call dispatch for the vulnerability
// tools (P4-T5 read tools + P3-T4/P3-T5 write tools):
// search_vulnerabilities, get_vulnerability,
// list_vuln_lookup_options, get_vuln_tags_stats (read), and
// submit_vulnerability, update_vulnerability,
// transition_vulnerability, add_vulnerability_comment (write, defined
// in vuln_write.go) plus upload_vulnerability_attachment (write,
// defined in vuln_upload.go — a three-step orchestration, not an
// EndpointRef mapping). It plugs into the tools/call main flow (P4-T4,
// call.go) by replacing the callVulnTool stub below, and reuses
// call.go's resolveEndpoint / callEndpoint / errorResult / ArgError so
// the vuln tools behave exactly like the CAASM ones.

func init() { callVulnTool = dispatchVuln }

// dispatchVuln routes the vulnerability tool names to their handlers.
// handled is true for the vuln tools and false (with nil data
// and nil err) for any other tool name. Argument problems are returned
// as *ArgError; backend/transport failures are returned as the
// structured {"error": ...} payload in data — same convention as
// call.go's CAASM path.
func dispatchVuln(ctx context.Context, c *client.Client, name string, args map[string]any) (data any, handled bool, err error) {
	switch name {
	case "search_vulnerabilities":
		return vulnCallSingle(ctx, c, name, 0, args)
	case "get_vulnerability":
		return vulnGetVulnerability(ctx, c, args)
	case "list_vuln_lookup_options":
		return vulnLookupOptions(ctx, c, args)
	case "get_vuln_tags_stats":
		return vulnCallSingle(ctx, c, name, 0, args)
	case "submit_vulnerability",
		"update_vulnerability",
		"transition_vulnerability",
		"add_vulnerability_comment":
		return vulnCallSingle(ctx, c, name, 0, args)
	case "upload_vulnerability_attachment":
		// 编排工具（P3-T5）：预签名 → PutRaw 直传 → 确认，三步由
		// vulnUploadAttachment 代跑，非 callEndpoint 单端点映射。
		return vulnUploadAttachment(ctx, c, args)
	default:
		return nil, false, nil
	}
}

// vulnCallSingle handles the single-endpoint tools: args are mapped
// onto the endpoint's query string per its ParamMap (read tools) or
// into the JSON request body per its BodyMap (write tools) —
// callEndpoint dispatches on the endpoint's Method.
func vulnCallSingle(ctx context.Context, c *client.Client, name string, epIdx int, args map[string]any) (any, bool, error) {
	ep, err := vulnEndpointAt(name, epIdx)
	if err != nil {
		return nil, true, err
	}
	data, err := getData(ctx, c, ep, args)
	if err != nil {
		return vulnErrOrResult(err)
	}
	return data, true, nil
}

// vulnGetVulnerability aggregates the three get_vulnerability endpoints
// (detail + events + comments) into
// {"vulnerability": ..., "events": ..., "comments": ...}. A detail
// failure (e.g. 404) is passed through directly without calling the
// other two endpoints.
func vulnGetVulnerability(ctx context.Context, c *client.Client, args map[string]any) (any, bool, error) {
	publicID, _ := args["public_id"].(string)
	if publicID == "" {
		return nil, true, &ArgError{Msg: "缺少必填参数 public_id（漏洞 Public ID，字符串，来自 search_vulnerabilities 结果，非数字 ID）"}
	}

	detailEp, err := vulnEndpointAt("get_vulnerability", 0)
	if err != nil {
		return nil, true, err
	}
	detail, err := getData(ctx, c, detailEp, args)
	if err != nil {
		return vulnErrOrResult(err)
	}

	merged := map[string]any{"vulnerability": detail}
	for i, key := range []string{"events", "comments"} {
		ep, err := vulnEndpointAt("get_vulnerability", i+1)
		if err != nil {
			return nil, true, err
		}
		data, err := getData(ctx, c, ep, args)
		if err != nil {
			return vulnErrOrResult(err)
		}
		merged[key] = data
	}
	return merged, true, nil
}

// vulnLookupKinds maps the list_vuln_lookup_options kind argument to the
// index of its backing endpoint in the tool's Endpoints list.
var vulnLookupKinds = map[string]int{
	"tags":     0, // /api/v1/vulnerability-tags
	"types":    1, // /api/v1/vulnerability-types
	"sources":  2, // /api/v1/vulnerability-sources
	"channels": 3, // /api/v1/vulnerability-discovery-channels
}

// vulnLookupKindHint lists the legal kind values for error messages.
const vulnLookupKindHint = "tags（标签）/types（类型）/sources（来源）/channels（发现渠道）"

// vulnLookupOptions dispatches list_vuln_lookup_options to one of the
// four dictionary endpoints selected by args["kind"]. kind is
// dispatch-only: it maps no backend parameter, so it never reaches the
// query string.
func vulnLookupOptions(ctx context.Context, c *client.Client, args map[string]any) (any, bool, error) {
	kind, _ := args["kind"].(string)
	idx, ok := vulnLookupKinds[kind]
	if !ok {
		if kind == "" {
			return nil, true, &ArgError{Msg: fmt.Sprintf("缺少必填参数 kind，合法值：%s", vulnLookupKindHint)}
		}
		return nil, true, &ArgError{Msg: fmt.Sprintf("参数 kind 取值 %q 非法，合法值：%s", kind, vulnLookupKindHint)}
	}
	ep, err := vulnEndpointAt("list_vuln_lookup_options", idx)
	if err != nil {
		return nil, true, err
	}
	data, err := getData(ctx, c, ep, args)
	if err != nil {
		return vulnErrOrResult(err)
	}
	return data, true, nil
}

// vulnEndpointAt returns the idx-th endpoint registered for toolName.
func vulnEndpointAt(toolName string, idx int) (EndpointRef, error) {
	tool, ok := findTool(toolName)
	if !ok {
		return EndpointRef{}, fmt.Errorf("tools: tool %q not registered", toolName)
	}
	if idx < 0 || idx >= len(tool.Endpoints) {
		return EndpointRef{}, fmt.Errorf("tools: endpoint #%d of %q not registered", idx, toolName)
	}
	return tool.Endpoints[idx], nil
}

// vulnErrOrResult maps a getData failure to the dispatch triple:
// argument problems (*ArgError, e.g. from resolveEndpoint) propagate as
// Go errors like in call.go's CAASM path; backend business errors and
// transport failures become the structured error payload via
// errorResult.
func vulnErrOrResult(err error) (any, bool, error) {
	var argErr *ArgError
	if errors.As(err, &argErr) {
		return nil, true, argErr
	}
	return errorResult(err), true, nil
}
