"use client";

import { useEffect, useMemo, useState } from "react";
import { Card, Collapse, Space, Spin, Table, Tag, Typography } from "antd";
import type { ColumnsType } from "antd/es/table";
import Link from "next/link";

import { PageStateNotice } from "@/components/common/page-state-notice";
import { fetchOpenAPISpec } from "@/lib/api/openapi";
import type { SwaggerOperation, SwaggerParameter, SwaggerSpec } from "@/lib/api/openapi";

const { Title, Paragraph, Text } = Typography;

type EndpointRow = {
  method: "get" | "post" | "put" | "delete" | "patch";
  path: string;
  op: SwaggerOperation;
};

type EndpointGroup = {
  key: string;
  label: string;
  endpoints: EndpointRow[];
};

const METHOD_ORDER: Record<EndpointRow["method"], number> = { get: 0, post: 1, put: 2, patch: 3, delete: 4 };

function groupEndpoints(spec: SwaggerSpec): EndpointGroup[] {
  const caasm: EndpointRow[] = [];
  const vuln: EndpointRow[] = [];
  for (const [path, methods] of Object.entries(spec.paths ?? {})) {
    for (const method of Object.keys(METHOD_ORDER) as EndpointRow["method"][]) {
      const op = methods[method];
      if (!op) continue;
      (path.startsWith("/api/v1/caasm") ? caasm : vuln).push({ method, path, op });
    }
  }
  const byPathThenMethod = (a: EndpointRow, b: EndpointRow) =>
    a.path.localeCompare(b.path) || METHOD_ORDER[a.method] - METHOD_ORDER[b.method];
  return [
    { key: "caasm", label: "CAASM (Cyber Asset Attack Surface Management)", endpoints: caasm.sort(byPathThenMethod) },
    { key: "vuln", label: "Vulnerabilities", endpoints: vuln.sort(byPathThenMethod) },
  ];
}

const METHOD_TAG_COLOR: Record<EndpointRow["method"], string> = {
  get: "green",
  post: "blue",
  put: "orange",
  patch: "purple",
  delete: "red",
};

const parameterColumns: ColumnsType<SwaggerParameter> = [
  { title: "Name", dataIndex: "name", width: 200, render: (v: string) => <Text code>{v}</Text> },
  { title: "In", dataIndex: "in", width: 90, render: (v?: string) => <Tag>{v}</Tag> },
  {
    title: "Type",
    dataIndex: "type",
    width: 110,
    render: (v: string | undefined, row: SwaggerParameter) =>
      v ?? (row.schema?.$ref ? row.schema.$ref.replace("#/definitions/", "") : "-"),
  },
  {
    title: "Required",
    dataIndex: "required",
    width: 100,
    render: (v?: boolean) => (v ? <Tag color="red">required</Tag> : "-"),
  },
  { title: "Description", dataIndex: "description", render: (v?: string) => v ?? "-" },
];

function CodeBlock({ children }: { children: string }) {
  return (
    <pre
      style={{
        background: "#0f172a",
        color: "#e2e8f0",
        padding: "12px 16px",
        borderRadius: 8,
        overflow: "auto",
        fontSize: 12.5,
        lineHeight: 1.6,
      }}
    >
      {children}
    </pre>
  );
}

type MCPToolRow = { name: string; summary: string; notes?: string };

const MCP_TOOLS: MCPToolRow[] = [
  { name: "whoami", summary: "Resolve the caller's identity — user_id / employee_id / scopes — so agents can answer \"my …\" queries", notes: "no arguments; call first, then filter by submitter_user_id (submitted by me) or owner_employee_id (assigned to me)" },
  { name: "search_assets", summary: "Search the asset inventory (hosts, IPs, domains, services)", notes: "region/exposure/asset_service switch to the IP-asset view; view=services|environments returns filter enum values" },
  { name: "get_asset", summary: "Full asset detail with tags", notes: "requires id" },
  { name: "list_ports", summary: "Port exposure list with filters", notes: "id = port detail; view=stats|by_asset" },
  { name: "list_high_risk_ports", summary: "All high-risk open ports (e.g. 3306, 3389)" },
  { name: "list_dns_records", summary: "DNS records with type/owner/alert filters", notes: "id = record comments" },
  { name: "list_domains", summary: "Domain assets with registrar/zone/auto-renew filters", notes: "view=stats|registrars" },
  { name: "get_relationship_graph", summary: "Asset relationship graph (nodes + edges)" },
  { name: "list_cloud_accounts", summary: "Connected cloud accounts", notes: "id = account detail + its sync jobs" },
  { name: "list_sync_jobs", summary: "Asset sync jobs and their event stream", notes: "id = detail; id + limit/before_id/after_id = events (before_id and after_id are mutually exclusive)" },
  { name: "list_api_route_assets", summary: "Discovered API route assets", notes: "view=config = active discovery config" },
  { name: "search_vulnerabilities", summary: "Vulnerability search across 21 filters (severity, status, tags, dates, …)" },
  { name: "get_vulnerability", summary: "Vulnerability dossier: detail + lifecycle events + comments", notes: "requires public_id" },
  { name: "list_vuln_lookup_options", summary: "Filter dictionaries for vulnerability search", notes: "requires kind = tags|types|sources|channels" },
  { name: "get_vuln_tags_stats", summary: "Vulnerability counts aggregated by tag" },
  { name: "submit_vulnerability", summary: "Submit a new vulnerability", notes: "requires title/severity_level/status/discoverer_employee_id; requires vuln:submit_findings scope; submitter is forced to the key owner, timestamps are server-set; tags not supported yet; reference images via attachment_id from upload_vulnerability_attachment" },
  { name: "update_vulnerability", summary: "Edit non-sensitive vulnerability fields", notes: "requires public_id; poc_text/verification_steps/has_poc_verification/submitted_at/sla_deadline are rejected with 403" },
  { name: "transition_vulnerability", summary: "Move a vulnerability through its status machine", notes: "requires public_id + status = retesting|fixed|false_positive|risk_accepted|active; retesting requires the caller to be the fixer" },
  { name: "add_vulnerability_comment", summary: "Comment on a vulnerability", notes: "requires public_id + content" },
  { name: "upload_vulnerability_attachment", summary: "Upload an image as vulnerability evidence", notes: "requires file_path (local to the machine running the MCP server); images only (png/jpeg/webp/gif, no SVG), ≤10MB; returns attachment_id" },
];

const mcpToolColumns: ColumnsType<MCPToolRow> = [
  { title: "Tool", dataIndex: "name", width: 230, render: (v: string) => <Text code>{v}</Text> },
  { title: "What it does", dataIndex: "summary" },
  { title: "Modes & required args", dataIndex: "notes", render: (v?: string) => v ?? "-" },
];

type MCPPromptExample = { key: string; title: string; prompt: string };

// Ready-to-paste prompts for driving the tools above from any AI client
// (they steer the tools, not a specific client). <ANGLE_BRACKETS> mark
// placeholders to adapt per call site; never put a real API key in a prompt.
const MCP_PROMPT_EXAMPLES: MCPPromptExample[] = [
  {
    key: "triage-my-queue",
    title: "Triage my queue (read-only)",
    prompt: `Use the security-center MCP tools:
1. Call whoami to resolve my user_id and employee_id.
2. Call search_vulnerabilities with owner_employee_id set to my employee_id and status=active, newest submissions first.
3. Summarize the top 5 as a table: public_id, title, severity, submitted_at — and give each detail-page URL as <site origin>/dashboard/vulnerabilities/<public_id>.`,
  },
  {
    key: "submit-with-image",
    title: "Submit a vulnerability with an image attachment (write, vuln:submit_findings)",
    prompt: `Use the security-center MCP tools to submit a vulnerability with screenshot evidence:
1. Call upload_vulnerability_attachment with file_path=<LOCAL_IMAGE_PATH> (a png/jpeg/webp/gif on the machine running the MCP server) and keep the returned attachment_id.
2. Call submit_vulnerability with title=<TITLE>, severity_level=high, status=active, discoverer_employee_id=<DISCOVERER_ID>, fixer_employee_id=<FIXER_ID>, plus poc_text / verification_steps / has_poc_verification as applicable. In the HTML description, embed the screenshot via <img data-sc-attachment-id="<ATTACHMENT_ID>">.
3. Call get_vulnerability with the returned public_id and confirm the attachment is linked.
4. Report the public_id and the detail-page URL.`,
  },
  {
    key: "request-retest",
    title: "Request a retest as the fixer (write, vuln:request_retest)",
    prompt: `Use the security-center MCP tools:
1. Call whoami to resolve my employee_id.
2. Call get_vulnerability for public_id=<PUBLIC_ID> and verify fixer_employee_id equals my employee_id — only the fixer can request a retest, and only from status active.
3. If it matches, call transition_vulnerability with public_id=<PUBLIC_ID>, status=retesting and a message describing the fix (what changed, where it is deployed).
4. Report the new status and the latest event.`,
  },
];

// Example payloads are built from the current origin (see buildExamples):
// on a production deployment the docs show the real URL, not localhost.
function buildExamples(baseUrl: string) {
  const curlExample = `curl -H "X-API-Key: sk-agent-your-own-key" \\
  ${baseUrl}/api/v1/caasm/assets?page=1&page_size=20`;

  const mcpConfigExample = `{
  "mcpServers": {
    "security-center": {
      "command": "/absolute/path/to/security-center-mcp",
      "env": {
        "SECURITY_CENTER_BASE_URL": "${baseUrl}",
        "SECURITY_CENTER_API_KEY": "sk-agent-your-own-key"
      }
    }
  }
}`;

  // The agent brief is a self-contained plain-text summary of this whole
  // page: copy it as context for any agent (chat, IDE, or fetch-only).
  // Prompt texts are reused from MCP_PROMPT_EXAMPLES so the two never drift.
  const promptSection = MCP_PROMPT_EXAMPLES.map((ex, i) => `4.${i + 1} ${ex.title}\n${ex.prompt}`).join("\n\n");
  const agentBrief = `SECURITY CENTER — AGENT INTEGRATION BRIEF (plain-text, self-contained)

1. CREDENTIALS
- Create an API key in the UI: Dashboard → OpenAPI → API Keys → Create Key. The plaintext key is shown exactly once.
- Send it on every API request via header: X-API-Key: sk-agent-...
  (interactive human sessions may also use Authorization: Bearer <JWT>)

2. MACHINE-READABLE CONTRACTS
- OpenAPI (Swagger 2.0): GET ${baseUrl}/api/v1/openapi.json — no auth required, ETag/304 caching, bare JSON document (no response envelope).
- MCP tools: the authoritative tool list is what tools/list returns from security-center-mcp; any rendered table is only a summary.
- This brief is also served as server-rendered HTML at ${baseUrl}/openapi/docs (no login, no JavaScript required) so fetch-only agents can read it directly.

3. MCP SERVER (stdio transport)
- Build: cd security-center-mcp && go build -o security-center-mcp .
- Client config (Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json; Cursor: ~/.cursor/mcp.json):
${mcpConfigExample}
- SECURITY_CENTER_API_KEY is required (min. 16 chars; the process exits otherwise). SECURITY_CENTER_BASE_URL defaults to http://127.0.0.1:8080.
- Fully restart the client after saving the config.

4. EXAMPLE PROMPTS (client-agnostic; adapt <PLACEHOLDERS>)
${promptSection}

5. RULES OF THE ROAD
- Response envelope: {code, message, data, timestamp} — business errors arrive as HTTP 200 with a non-zero code (401/403/404/429/…). The only exception is /api/v1/openapi.json, which returns the bare document.
- Quota: each API key is limited per minute (100 rpm by default); exceeding it returns code 429 with a Retry-After: 60 header.
- Permission model: a key's effective capability = the owner's RBAC permissions ∩ the key's declared scopes, enforced on every request.
- Write scopes (vuln:edit_details / vuln:request_retest / vuln:close_findings / vuln:submit_findings) always require vuln:view_findings alongside. Sensitive and administrative scopes (vuln:view_sensitive_details, vuln:edit_sensitive_details, vuln:manage_findings, vuln:authorize_viewers, vuln:assign_owner) are never granted to agents, and no DELETE endpoint is exposed.
- Write guardrails for agents: the submitter is forced to the key owner, submitted_at/sla_deadline are server-set, and edits to poc_text / verification_steps / has_poc_verification are rejected with 403. Every edit event records the previous values.
- Audit: every agent call is recorded in the audit log and attributed to the key owner; per-key usage stats are available from the API Keys page.`;

  return { curlExample, mcpConfigExample, agentBrief };
}

export function OpenAPIDocsContent({ initialBaseUrl }: { initialBaseUrl?: string }) {
  const [spec, setSpec] = useState<SwaggerSpec | null>(null);
  const [loading, setLoading] = useState(true);
  const [loadError, setLoadError] = useState<string | null>(null);

  useEffect(() => {
    let cancelled = false;
    (async () => {
      try {
        const data = await fetchOpenAPISpec();
        if (!cancelled) {
          setSpec(data);
          setLoadError(null);
        }
      } catch (error) {
        console.error("Failed to load OpenAPI spec", error);
        if (!cancelled) {
          setLoadError("The OpenAPI contract could not be loaded from /api/v1/openapi.json.");
        }
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();
    return () => {
      cancelled = true;
    };
  }, []);

  const groups = useMemo(() => (spec ? groupEndpoints(spec) : []), [spec]);
  const endpointCount = useMemo(() => groups.reduce((n, g) => n + g.endpoints.length, 0), [groups]);

  // Examples must match the deployment: prerender with a placeholder, then
  // swap in the real origin after mount (avoids hydration mismatch). The
  // public route passes initialBaseUrl from the request Host header so its
  // SSR HTML already carries the real origin for fetch-only agents.
  const [baseUrl, setBaseUrl] = useState(initialBaseUrl ?? "https://your-host");
  useEffect(() => {
    setBaseUrl(window.location.origin);
  }, []);
  const { curlExample, mcpConfigExample, agentBrief } = useMemo(() => buildExamples(baseUrl), [baseUrl]);

  return (
    <Space direction="vertical" size={24} style={{ width: "100%" }}>
      <div>
        <Title level={3} style={{ marginBottom: 4 }}>
          API Docs
        </Title>
        <Paragraph type="secondary" style={{ margin: 0 }}>
          Everything agents need to call the platform&apos;s open APIs: the OpenAPI contract, the endpoint
          reference, and the MCP server that exposes the same surface to AI clients. Keys with write scopes can
          also submit and triage vulnerabilities.
        </Paragraph>
      </div>

      <Card title="Quickstart">
        <Space direction="vertical" size={12} style={{ width: "100%" }}>
          <Paragraph style={{ marginBottom: 0 }}>
            1. Create an API key on the <Link href="/dashboard/api-keys">API Keys</Link> page. The plaintext key is
            shown only once — store it somewhere safe.
          </Paragraph>
          <Paragraph style={{ marginBottom: 0 }}>
            2. Send it on every request via the <Text code>X-API-Key</Text> header (interactive sessions may also
            use <Text code>Authorization: Bearer &lt;JWT&gt;</Text>):
          </Paragraph>
          <CodeBlock>{curlExample}</CodeBlock>
          <Paragraph type="secondary" style={{ marginBottom: 0 }}>
            The machine-readable contract (Swagger 2.0) is served at <Text code>GET /api/v1/openapi.json</Text>{" "}
            with ETag/304 caching. Both <Text code>agentApiKey</Text> and <Text code>bearerAuth</Text> are accepted.
          </Paragraph>
        </Space>
      </Card>

      <Card title="Machine-readable access (for AI agents)">
        <Space direction="vertical" size={12} style={{ width: "100%" }}>
          <Paragraph style={{ marginBottom: 0 }}>
            This page is also served as server-rendered HTML at <Text code>/openapi/docs</Text> — no login and no
            JavaScript required, so fetch-only agents can read it with a plain HTTP GET. The API contract is{" "}
            <Text code>GET /api/v1/openapi.json</Text> (no auth, ETag caching); MCP clients should treat{" "}
            <Text code>tools/list</Text> as the authoritative tool list.
          </Paragraph>
          <Paragraph style={{ marginBottom: 0 }}>
            Working in a chat or IDE agent instead? Copy the brief below as its context — it is self-contained
            (credentials, contracts, MCP setup, example prompts, and the rules of the road):
          </Paragraph>
          <CodeBlock>{agentBrief}</CodeBlock>
        </Space>
      </Card>

      <Card title={`Endpoint Reference${endpointCount ? ` (${endpointCount})` : ""}`}>
        {loading ? (
          <Space style={{ width: "100%", justifyContent: "center", padding: 24 }}>
            <Spin />
          </Space>
        ) : loadError ? (
          <PageStateNotice tone="error" compact title="Contract unavailable" description={loadError} />
        ) : (
          <>
            <Paragraph type="secondary">
              Read-only <Tag color="green">GET</Tag> endpoints are open to every agent key; write{" "}
              <Tag color="blue">POST</Tag>/<Tag color="orange">PUT</Tag> endpoints additionally require the matching
              write scope (<Text code>vuln:edit_details</Text> / <Text code>vuln:request_retest</Text> /{" "}
              <Text code>vuln:close_findings</Text> / <Text code>vuln:submit_findings</Text>). Rendered from the
              live contract below.
            </Paragraph>
            <Collapse
              defaultActiveKey={["caasm", "vuln"]}
              items={groups.map((group) => ({
                key: group.key,
                label: `${group.label} · ${group.endpoints.length} endpoints`,
                children: (
                  <Collapse
                    ghost
                    items={group.endpoints.map(({ method, path, op }) => ({
                      key: `${method} ${path}`,
                      label: (
                        <Space size={8}>
                          <Tag color={METHOD_TAG_COLOR[method]} style={{ marginInlineEnd: 0 }}>
                            {method.toUpperCase()}
                          </Tag>
                          <Text code style={{ fontSize: 12 }}>{path}</Text>
                          {op.summary ? (
                            <Text type="secondary" style={{ fontSize: 12 }}>{op.summary}</Text>
                          ) : null}
                        </Space>
                      ),
                      children: op.parameters?.length ? (
                        <Table<SwaggerParameter>
                          rowKey={(p) => `${p.in}:${p.name}`}
                          columns={parameterColumns}
                          dataSource={op.parameters}
                          pagination={false}
                          size="small"
                        />
                      ) : (
                        <Text type="secondary">No parameters.</Text>
                      ),
                    }))}
                  />
                ),
              }))}
            />
          </>
        )}
      </Card>

      <Card title="MCP Server (AI client integration)">
        <Space direction="vertical" size={12} style={{ width: "100%" }}>
          <Paragraph style={{ marginBottom: 0 }}>
            <Text code>security-center-mcp</Text> is a stdio MCP server that exposes {MCP_TOOLS.length} tools to AI
            clients (Claude Desktop, Cursor, …) — the {MCP_TOOLS.length - 6} read-only query tools, the whoami
            identity tool (resolves user_id / employee_id / scopes for &quot;my …&quot; queries), plus 5
            vulnerability write tools. It forwards <Text strong>your own API key</Text> on every call, so usage is
            audited to the key owner — there is no shared key pool.
          </Paragraph>
          <Paragraph style={{ marginBottom: 0 }}>
            1. Build the binary: <Text code>cd security-center-mcp && go build -o security-center-mcp .</Text>
          </Paragraph>
          <Paragraph style={{ marginBottom: 0 }}>
            2. Point your client at it (Claude Desktop:{" "}
            <Text code>~/Library/Application Support/Claude/claude_desktop_config.json</Text>; Cursor:{" "}
            <Text code>~/.cursor/mcp.json</Text>):
          </Paragraph>
          <CodeBlock>{mcpConfigExample}</CodeBlock>
          <Paragraph type="secondary" style={{ marginBottom: 0 }}>
            <Text code>SECURITY_CENTER_API_KEY</Text> is required (min. 16 chars, the process exits otherwise);{" "}
            <Text code>SECURITY_CENTER_BASE_URL</Text> defaults to <Text code>http://127.0.0.1:8080</Text> for local
            development — set it to your deployment URL in production (the example above already uses this
            site&apos;s origin). Fully restart the client after saving. The authoritative list is what{" "}
            <Text code>tools/list</Text> returns — the table below is a summary.
          </Paragraph>
          <Table<MCPToolRow>
            rowKey="name"
            columns={mcpToolColumns}
            dataSource={MCP_TOOLS}
            pagination={false}
            size="small"
          />
          <Paragraph style={{ marginBottom: 0 }}>
            3. Drive it with natural language — prompts are client-agnostic (they steer the tools above, not a
            specific client). Paste one into your AI client and adapt the <Text code>&lt;PLACEHOLDERS&gt;</Text>:
          </Paragraph>
          <Collapse
            ghost
            items={MCP_PROMPT_EXAMPLES.map((ex) => ({
              key: ex.key,
              label: ex.title,
              children: <CodeBlock>{ex.prompt}</CodeBlock>,
            }))}
          />
        </Space>
      </Card>

      <Card title="Operational notes">
        <ul style={{ margin: 0, paddingLeft: 20, lineHeight: 2 }}>
          <li>
            <Text strong>Quota:</Text> each API key is limited per minute (100 rpm by default); exceeding it
            returns code <Text code>429</Text> with a <Text code>Retry-After: 60</Text> header.
          </li>
          <li>
            <Text strong>Response envelope:</Text> business responses are{" "}
            <Text code>{"{code, message, data, timestamp}"}</Text> — business errors arrive as HTTP 200 with a
            non-zero <Text code>code</Text> (401/403/404/429/…). The only exception is{" "}
            <Text code>/api/v1/openapi.json</Text>, which returns the bare document.
          </li>
          <li>
            <Text strong>Permission model:</Text> a key&apos;s effective capability = the owner&apos;s RBAC
            permissions ∩ the key&apos;s declared scopes, enforced on every request. Write scopes (
            <Text code>vuln:edit_details</Text> / <Text code>vuln:request_retest</Text> /{" "}
            <Text code>vuln:close_findings</Text> / <Text code>vuln:submit_findings</Text> — submit new
            vulnerabilities) always require <Text code>vuln:view_findings</Text> alongside.
            Sensitive and administrative scopes (<Text code>vuln:view_sensitive_details</Text>,{" "}
            <Text code>vuln:edit_sensitive_details</Text>, <Text code>vuln:manage_findings</Text>,{" "}
            <Text code>vuln:authorize_viewers</Text>, <Text code>vuln:assign_owner</Text>) are never granted to
            agents, and no DELETE endpoint is exposed.
          </li>
          <li>
            <Text strong>Write guardrails:</Text> for agent calls the submitter is forced to the key owner, SLA
            timestamps are server-set, and edits to sensitive fields (PoC / verification steps) are rejected with
            403. Every edit event records the previous values, so mistakes can be rolled back precisely.
          </li>
          <li>
            <Text strong>Audit:</Text> every agent call is recorded in the audit log and attributed to the key
            owner; per-key usage stats are available from the API Keys page.
          </li>
        </ul>
      </Card>
    </Space>
  );
}
