import { Effect } from "effect";
import { assertPublicUrl } from "@reactive-agents/runtime-shim";

import type { ToolDefinition } from "../types.js";
import { ToolExecutionError } from "../errors.js";

/** Max redirect hops to follow while re-validating each against the egress guard. */
const MAX_REDIRECTS = 5;

export const httpGetTool: ToolDefinition = {
  name: "http-get",
  description:
    "Fetch content from a specific URL via HTTP GET. " +
    "Use when you have an exact URL to retrieve (API endpoint, direct link, web page). " +
    "HTML pages are automatically stripped to plain text so you can read them directly. " +
    "JSON responses are parsed into objects. " +
    "Returns the page text content (status prefix on error). " +
    "For large results, the text is stored automatically — use recall(key, full: true) to retrieve everything. " +
    "Tip: use | transform: to extract a specific field, e.g. http-get(url) | transform: result.slice(0, 2000)",
  parameters: [
    {
      name: "url",
      type: "string",
      description:
        "The full URL to fetch. Must include the scheme (https:// or http://). " +
        "Examples: 'https://api.example.com/v1/data', 'https://jsonplaceholder.typicode.com/posts/1'.",
      required: true,
    },
    {
      name: "headers",
      type: "object",
      description:
        "Optional HTTP headers as a JSON object of key-value string pairs. " +
        "Example: { \"Authorization\": \"Bearer mytoken\", \"Accept\": \"application/json\" }. " +
        "Omit this parameter entirely if no special headers are needed.",
      required: false,
    },
  ],
  returnType:
    "{ status: number, statusText: string, body: string | object }",
  category: "http",
  riskLevel: "medium",
  timeoutMs: 15_000,
  requiresApproval: false,
  source: "builtin",
  // Sprint 3.4 Scaffold 1 — http-get fetches ONE URL per call. When a
  // task mentions multiple URLs (the per-entity case), the classifier
  // should multiply minCalls accordingly.
  cardinality: "per-entity",
};

export const httpGetHandler = (
  args: Record<string, unknown>,
): Effect.Effect<unknown, ToolExecutionError> =>
  Effect.tryPromise({
    try: async () => {
      const url = args.url as string;
      const headers = (args.headers as Record<string, string>) ?? {};

      // Egress guard (F6): the URL is model-controlled. Validate the target —
      // and every redirect hop — is public before fetching, so a prompt-injected
      // agent cannot reach cloud metadata (169.254.169.254) or internal hosts.
      // Set RA_HTTP_ALLOW_PRIVATE=1 to permit trusted loopback/private targets.
      const guard = { allowPrivate: process.env.RA_HTTP_ALLOW_PRIVATE === "1" };
      let current = (await assertPublicUrl(url, guard)).toString();
      let response = await fetch(current, { method: "GET", headers, redirect: "manual" });
      for (let hop = 0; hop < MAX_REDIRECTS; hop++) {
        if (response.status < 300 || response.status >= 400) break;
        const location = response.headers.get("location");
        if (!location) break;
        current = (await assertPublicUrl(new URL(location, current).toString(), guard)).toString();
        response = await fetch(current, { method: "GET", headers, redirect: "manual" });
      }
      const contentType = response.headers.get("content-type") ?? "";

      let body: unknown;
      if (contentType.includes("application/json")) {
        body = await response.json();
      } else {
        body = await response.text();
      }

      return {
        status: response.status,
        statusText: response.statusText,
        body,
      };
    },
    catch: (e) =>
      new ToolExecutionError({
        message: `HTTP GET failed: ${e}`,
        toolName: "http-get",
        cause: e,
      }),
  });
