import { NextResponse, type NextRequest, after } from "next/server";
import { classifyAiHit } from "./crawlers";
import { setRequestPathHeaders } from "./path";
import { usesPublishedV2 } from "./delivery-v2";
import { reportObservedPath } from "./report-v2";

/**
 * SmKing AI traffic ingestion proxy (v0.12+, Next.js 16 `proxy.ts`).
 *
 * Drop-in install:
 *
 * ```ts
 * // proxy.ts (project root)
 * import { smkingProxy } from "@soloworks/smking-next/proxy";
 *
 * export const proxy = smkingProxy({
 *   apiKey: process.env.SMKING_API_KEY!,
 * });
 *
 * export const config = {
 *   matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
 * };
 * ```
 *
 * What it does:
 *   - Inspects each request's UA + referer
 *   - If it's an AI bot or AI-referred human session, batches one
 *     ingestion call via Vercel `after()` (post-response background)
 *   - v2 page discovery is independently reported through a bounded signed
 *     POST; rendering and content cache lookups have no reporting side effects
 *
 * Composing with an existing proxy:
 *
 * ```ts
 * import { composeProxy, smkingProxy } from "@soloworks/smking-next/proxy";
 * import { customerProxy } from "./their-proxy";
 *
 * export const proxy = composeProxy([
 *   smkingProxy({ apiKey: process.env.SMKING_API_KEY! }),
 *   customerProxy,
 * ]);
 * ```
 *
 * Fail-open posture for telemetry (independent of content reads):
 *   - No `apiKey` → no-op, customer site keeps running
 *   - No `baseUrl` / `SMKING_BASE_URL` → no-op
 *   - Ingestion endpoint down / 4xx / 5xx → silent skip, next request retries
 *
 * Why `after()` (not fire-and-forget `fetch`):
 *   - Vercel serverless kills the function once the response flushes,
 *     which can drop a naked fetch before it's sent
 *   - `after()` keeps the function alive within the existing lifetime
 *     for background work — designed exactly for this case
 */
export interface SmkingProxyConfig {
  /** Site's public API key (`pk_*`). Falls back to `SMKING_API_KEY` env. */
  apiKey?: string;
  /** Ingestion base URL. Falls back to `SMKING_BASE_URL` env. */
  baseUrl?: string;
}

export type ProxyMiddleware = (
  request: NextRequest,
) => NextResponse | Promise<NextResponse>;

const INGEST_TIMEOUT_MS = 2000;

/** Observations are page discovery, not API, asset, audit or prefetch traffic. */
function isPageObservation(request: NextRequest): boolean {
  const path = request.nextUrl.pathname;
  const destination = request.headers.get("sec-fetch-dest");
  return request.method === "GET"
    && !/^\/(?:api|_next)(?:\/|$)/.test(path)
    && !/\.(?:txt|xml|json|webmanifest|ico|css|m?js|map|png|jpe?g|gif|svg|webp|avif|bmp|tiff?|woff2?|ttf|otf|eot|mp4|webm|mov|mp3|wav|ogg|m4a|pdf|zip|gz)$/i.test(path)
    && (!destination || destination === "document" || destination === "empty")
    && !request.headers.has("next-router-prefetch")
    && request.headers.get("purpose")?.toLowerCase() !== "prefetch"
    && request.headers.get("sec-purpose")?.toLowerCase().includes("prefetch") !== true
    && request.headers.get("x-smking-origin-mode")?.trim().toLowerCase() !== "raw";
}

/**
 * Build the proxy middleware. Returns a no-op middleware when the
 * required env / config is missing so dev / staging installs without
 * env never crash.
 */
export function smkingProxy(config: SmkingProxyConfig = {}): ProxyMiddleware {
  return (request: NextRequest) => {
    // Server Components cannot read the current pathname from `headers()` on
    // Vercel unless middleware forwards it. Mutating this request keeps the
    // host proxy's redirect, rewrite, cookie, and response behavior intact.
    setRequestPathHeaders(request.headers, request.nextUrl);

    const apiKey = config.apiKey ?? process.env.SMKING_API_KEY;
    const baseUrl = (config.baseUrl ?? process.env.SMKING_BASE_URL)?.replace(
      /\/$/,
      "",
    );
    if (!apiKey || !baseUrl) {
      return NextResponse.next();
    }

    // The request already exists here, including when Next serves cached
    // HTML. No headers()/connection() or content GET is needed in rendering.
    // Scheduling/report failures must not interfere with the host response.
    try {
      if (usesPublishedV2() && isPageObservation(request)) {
        reportObservedPath({ apiKey, baseUrl, path: request.nextUrl.pathname, url: request.nextUrl.toString() });
      }
    } catch { /* Optional observation is best-effort. */ }

    const ua = request.headers.get("user-agent");
    const referer = request.headers.get("referer");
    const hit = classifyAiHit(ua, referer);
    if (!hit) return NextResponse.next();

    const path = request.nextUrl.pathname;
    const pageUrl = request.nextUrl.toString();

    try {
      after(async () => {
        try {
          await fetch(`${baseUrl}/api/v1/crawler-hit`, {
            method: "POST",
            headers: {
              "content-type": "application/json",
              "x-public-key": apiKey,
            },
            body: JSON.stringify({
              hits: [
                {
                  bot: hit.bot,
                  bot_category: hit.botCategory,
                  purpose: hit.purpose,
                  path,
                  page_url: pageUrl,
                  user_agent: ua,
                  referer,
                  timestamp: new Date().toISOString(),
                },
              ],
            }),
            signal: AbortSignal.timeout(INGEST_TIMEOUT_MS),
          });
        } catch {
          // Ingestion failure must NEVER bubble to the customer site.
          // Next request will retry — losing a hit is acceptable.
        }
      });
    } catch { /* A failed background registration cannot block the page. */ }

    return NextResponse.next();
  };
}

/**
 * Run middlewares in sequence. The first one to return a non-`next()`
 * response (redirect / rewrite / abort) short-circuits the chain.
 *
 * NOTE: this assumes each middleware in the chain only adds headers or
 * passes through — it does NOT compose response bodies. Use cases:
 *   - smkingProxy (telemetry only) + customer auth proxy
 *   - smkingProxy + customer feature flag proxy
 */
export function composeProxy(handlers: ProxyMiddleware[]): ProxyMiddleware {
  return async (request: NextRequest) => {
    for (const h of handlers) {
      const result = await h(request);
      if (!isPassThrough(result)) return result;
    }
    return NextResponse.next();
  };
}

function isPassThrough(res: NextResponse): boolean {
  // `NextResponse.next()` carries `x-middleware-next: 1` internally.
  // Public surface: `res.headers.get('x-middleware-next')` returns "1"
  // for a pass-through. Anything else (redirect, rewrite, body) is a
  // terminal response.
  return res.headers.get("x-middleware-next") === "1";
}
