import { CliError, errorMessage } from "./errors.js";
import { success } from "./output.js";
import type { RequestOptions, RuntimeContext } from "./types.js";

function headers(ctx: RuntimeContext, requireAuth: boolean, requireProject: boolean): Record<string, string> {
  const out: Record<string, string> = {
    Accept: "application/json, text/plain, */*",
    "X-Request-Id": ctx.requestId,
    ...ctx.auth.headers,
  };
  if (requireAuth) {
    if (!ctx.auth.accessToken) {
      throw new CliError("认证失败", "UNAUTHENTICATED", "missing access token for agent context", 3, false, null, ["provide access_token via --auth-stdin or TJWATER_ACCESS_TOKEN env var"]);
    }
    out.Authorization = `Bearer ${ctx.auth.accessToken}`;
  } else if (ctx.auth.accessToken) out.Authorization = `Bearer ${ctx.auth.accessToken}`;
  if (requireProject) {
    if (!ctx.auth.projectId) throw new CliError("认证失败", "PROJECT_CONTEXT_REQUIRED", "missing project_id for agent context", 3, false, null, ["add project_id to auth context"]);
    out["X-Project-Id"] = ctx.auth.projectId;
  } else if (ctx.auth.projectId) out["X-Project-Id"] = ctx.auth.projectId;
  return out;
}

function stringifyParam(value: unknown): string {
  if (typeof value === "boolean") return value ? "True" : "False";
  return String(value);
}

function appendParams(url: URL, params: Record<string, unknown> = {}): void {
  for (const [key, value] of Object.entries(params)) {
    if (value === undefined || value === null) continue;
    if (Array.isArray(value)) value.forEach((item) => url.searchParams.append(key, stringifyParam(item)));
    else url.searchParams.set(key, stringifyParam(value));
  }
}

export async function requestJson(ctx: RuntimeContext, request: RequestOptions): Promise<[unknown, number]> {
  const { method, path, params, body, requireAuth = true, requireProject = false } = request;
  const url = new URL(`/api/v1${path}`, ctx.server.replace(/\/+$/, ""));
  appendParams(url, params);
  const started = performance.now();
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), ctx.timeout * 1000);
  let response: Response;
  try {
    response = await fetch(url, {
      method: method.toUpperCase(),
      headers: {
        ...headers(ctx, requireAuth, requireProject),
        ...(body === undefined ? {} : { "Content-Type": "application/json" }),
      },
      body: body === undefined ? undefined : JSON.stringify(body),
      signal: controller.signal,
    });
  } catch (error) {
    if (error instanceof Error && error.name === "AbortError") throw new CliError("请求超时", "REQUEST_TIMEOUT", `request timed out after ${ctx.timeout} seconds`, 7, true);
    throw new CliError("连接失败", "REQUEST_FAILED", errorMessage(error), 7, true);
  } finally {
    clearTimeout(timer);
  }
  const durationMs = Math.trunc(performance.now() - started);
  const contentType = response.headers.get("content-type")?.toLowerCase() ?? "";
  const text = response.status === 204 ? "" : await response.text();
  let payload: unknown = {};
  if (contentType.includes("application/json") && text) payload = JSON.parse(text);
  else if (text) payload = { report: text };
  if (!response.ok) {
    const record = payload && typeof payload === "object" && !Array.isArray(payload) ? (payload as Record<string, unknown>) : {};
    const message = typeof record.detail === "string" ? record.detail : typeof record.message === "string" ? record.message : text || `http ${response.status}`;
    throw new CliError("请求失败", `HTTP_${response.status}`, message, mapStatus(response.status), response.status >= 500);
  }
  if (payload && typeof payload === "object" && !Array.isArray(payload)) {
    const record = payload as Record<string, unknown>;
    if (record.status === "error") throw new CliError("服务端错误", "SERVER_ERROR", String(record.message || "server returned error status"), 7, false, payload);
  }
  return [payload, durationMs];
}

function mapStatus(status: number): number {
  if (status === 400 || status === 422) return 2;
  if (status === 401) return 3;
  if (status === 403) return 4;
  if (status === 404) return 5;
  if (status === 409 || status === 412) return 6;
  return 7;
}

export async function emitApi(ctx: RuntimeContext, summary: string, request: RequestOptions, nextCommands: string[] = []): Promise<void> {
  const [data, durationMs] = await requestJson(ctx, request);
  success(summary, data, ctx, durationMs, nextCommands);
}
