import { createHmac, randomUUID } from "node:crypto";
import { after } from "next/server";
import { SDK_VERSION } from "./version";

const seen = new Map<string, number>();
const MAX_SEEN = 500;
const REPORT_INTERVAL_MS = 60_000;

/** Shared bounded transport; scheduled health reports carry no visitor observations. */
export async function sendSdkReport(input: { apiKey: string; baseUrl: string; host: string; paths: string[] }): Promise<boolean> {
  const secret = process.env.SMKING_WEBHOOK_SECRET;
  if (!secret) return false;
  const body = JSON.stringify({
    key: input.apiKey, report_id: randomUUID(), observed_at: new Date().toISOString(),
    sdk_meta: {
      sdk: "next", sdk_version: SDK_VERSION, app_env: process.env.NODE_ENV ?? null, host: input.host,
      cms_base_path: process.env.SMKING_CMS_PATH?.startsWith("/") ? process.env.SMKING_CMS_PATH : null,
    },
    paths: input.paths,
  });
  const signature = createHmac("sha256", secret).update("smking-sdk-report-v2\n").update(body).digest("hex");
  try {
    const result = await fetch(`${input.baseUrl.replace(/\/$/, "")}/api/v2/sdk/report`, {
      method: "POST", headers: { "content-type": "application/json", "x-smking-report-signature": `sha256=${signature}` },
      body, cache: "no-store", signal: AbortSignal.timeout(2_000),
    });
    return result.ok;
  } catch { return false; }
}

/** Proxy reports are independent of content GET/rendering and run after the response. */
export function reportObservedPath(input: {
  apiKey: string;
  baseUrl: string;
  path: string;
  url?: string;
}): void {
  const secret = process.env.SMKING_WEBHOOK_SECRET;
  if (!secret || !input.url || !input.path.startsWith("/") || input.path.startsWith("//") || /[?#\\]/.test(input.path)) return;
  let host: string;
  try {
    const siteUrl = new URL(input.url);
    if (siteUrl.protocol !== "https:" && siteUrl.protocol !== "http:") return;
    host = siteUrl.host;
  } catch { return; }

  const key = JSON.stringify([input.baseUrl, input.apiKey, host, input.path]);
  const now = Date.now();
  if (now - (seen.get(key) ?? 0) < REPORT_INTERVAL_MS) return;
  for (const [candidate, queuedAt] of seen) {
    if (now - queuedAt > REPORT_INTERVAL_MS) seen.delete(candidate);
  }
  if (seen.size >= MAX_SEEN) return;
  seen.set(key, now);
  try {
    after(async () => {
      if (!await sendSdkReport({ ...input, host, paths: [input.path] })) seen.delete(key);
    });
  } catch {
    seen.delete(key); // Static export and non-request scopes cannot schedule work.
  }
}
