import type { WorkbenchCommand, WorkbenchMode } from "./contracts.ts";
import { createWorkbenchKafkaRefreshHandoff, workbenchKafkaRefreshErrorPayload } from "../cloud/workbench-kafka-refresh-handoff.ts";

export function createWorkbenchHttpApp(options: { dispatch: (command: WorkbenchCommand) => Promise<any>; snapshot?: () => Promise<{ sessions: Record<string, Record<string, unknown>>; turns: Record<string, Record<string, unknown>> }>; authorization?: string; mode?: WorkbenchMode; kafkaEventBridge?: any; close?: () => Promise<void> }) {
  return {
    async fetch(request: Request): Promise<Response> {
      try {
        const url = new URL(request.url);
        if (url.pathname === "/health/live") return json(200, { ok: true, serviceId: "hwlab-workbench-api", status: "live" });
        if (url.pathname === "/health/ready") return resultResponse(await options.dispatch({ operation: "health" }));
        if (options.snapshot) {
          if (url.pathname === "/auth/session" || url.pathname === "/auth/bootstrap") return json(200, nativeAuthSession(options.mode));
          if (url.pathname === "/auth/login" && request.method === "POST") return Response.json(nativeAuthSession(options.mode), { status: 200, headers: { "set-cookie": "hwlab_session=native-test; Path=/; HttpOnly; SameSite=Lax", "cache-control": "no-store" } });
          if (url.pathname === "/v1/users/me") return json(200, { ok: true, actor: { id: "usr_native", role: "user" }, authMethod: "native-test" });
          if (url.pathname === "/v1/access/status") return json(200, { ok: true, status: "active", actor: { id: "usr_native", role: "user" }, nav: ["workbench.code"] });
          if (url.pathname === "/v1/provider-profiles") {
            const profile = options.mode === "agentrun-native" ? "gpt.pika" : "native-test";
            return json(200, { ok: true, items: [{ profile, backendProfile: profile, configured: true }], count: 1, mode: options.mode ?? "native-test" });
          }
          if (url.pathname === "/v1/dashboard/summary") return json(200, { ok: true, status: "ready", mode: options.mode ?? "native-test" });
          if (url.pathname === "/v1/caserun/cases") return json(200, { ok: true, status: "unavailable", cases: [], count: 0, mode: options.mode ?? "native-test" });
          if (url.pathname === "/v1/hwpod/specs") return json(200, { ok: true, status: "unavailable", specs: [], mode: options.mode ?? "native-test" });
          if (url.pathname === "/v1/hwpod-node-ops") return json(200, { ok: true, status: "unavailable", events: [], groups: [], mode: options.mode ?? "native-test" });
          if (url.pathname === "/v1/web-performance" && request.method === "POST") return json(202, { ok: true, status: "accepted", persisted: false, mode: options.mode ?? "native-test" });
          if (url.pathname === "/v1/workbench/events" && request.method === "GET") return nativeEventStream(options, url);
          if (url.pathname === "/v1/workbench/sessions" && request.method === "GET") return nativeSessionList(options, url);
          const sessionMatch = /^\/v1\/workbench\/sessions\/([^/]+)(?:\/(messages))?$/u.exec(url.pathname);
          if (sessionMatch && request.method === "GET") return nativeSessionDetail(options, decodeURIComponent(sessionMatch[1]), sessionMatch[2] === "messages");
          const turnMatch = /^\/v1\/workbench\/turns\/([^/]+)$/u.exec(url.pathname);
          if (turnMatch && request.method === "GET") return nativeTurn(options, decodeURIComponent(turnMatch[1]));
        }
        if (url.pathname === "/v1/workbench/commands" && request.method === "POST") {
          requireAuthorization(request, options);
          const command = await bodyObject(request) as WorkbenchCommand;
          return resultResponse(await options.dispatch(command), command.operation === "health" ? 200 : 202);
        }
        const actor = actorFrom(request, Boolean(options.snapshot));
        if (url.pathname === "/v1/agent/sessions" && request.method === "POST") { requireAuthorization(request, options); return legacyResponse(await options.dispatch({ operation: "session.create", actor, params: await bodyObject(request) }), 201); }
        if (url.pathname === "/v1/agent/chat" && request.method === "POST") {
          requireAuthorization(request, options);
          const params = await bodyObject(request);
          return legacyResponse(await options.dispatch({ operation: "turn.submit", actor, params, traceId: text(params.traceId) || undefined }), 202);
        }
        if (url.pathname === "/v1/agent/chat/cancel" && request.method === "POST") {
          requireAuthorization(request, options);
          const params = await bodyObject(request);
          return legacyResponse(await options.dispatch({ operation: "turn.cancel", actor, traceId: text(params.traceId), params }));
        }
        return json(404, { ok: false, error: { code: "not_found", message: "Workbench API route was not found" } });
      } catch (error: any) {
        return json(400, { ok: false, error: { code: error?.code ?? "workbench_http_error", message: error?.message ?? String(error) } });
      }
    },
    close: options.close ?? (async () => {})
  };
}

function nativeAuthSession(mode: WorkbenchMode = "native-test") {
  return {
    ok: true,
    authenticated: true,
    actor: { id: "usr_native", role: "user" },
    access: { nav: { profileId: "workbench-native", allowedIds: ["workbench.code"], valuesRedacted: true } },
    mode,
    valuesRedacted: true
  };
}

function requireAuthorization(request: Request, options: { snapshot?: unknown; authorization?: string }) {
  if (options.snapshot) return;
  const expected = text(options.authorization);
  const actual = text(request.headers.get("authorization"));
  if (!expected || actual !== expected) throw Object.assign(new Error("Workbench API internal authentication failed"), { code: "auth_required" });
}

async function nativeSessionList(options: { snapshot?: () => Promise<any>; mode?: WorkbenchMode; kafkaEventBridge?: any }, url: URL) {
  const state = await requiredSnapshot(options);
  const owner = text(url.searchParams.get("ownerUserId"));
  const kafkaSummary = options.mode === "agentrun-native" ? options.kafkaEventBridge?.workbenchSessionSummaries?.() : null;
  const kafkaSummaryBySession = new Map((Array.isArray(kafkaSummary?.sessions) ? kafkaSummary.sessions : []).map((session: any) => [text(session.sessionId), session]));
  const sessions = Object.values(state.sessions)
    .filter((session: any) => !owner || session.ownerUserId === owner)
    .map((session: any) => {
      const summary = kafkaSummaryBySession.get(text(session.sessionId)) as any;
      const nativeTestPreview = options.mode === "agentrun-native" ? null : firstUserMessagePreview(session.messages) ?? (text(session.firstUserMessagePreview) || null);
      return {
        ...session,
        firstUserMessagePreview: text(summary?.firstUserMessagePreview) || nativeTestPreview,
        lastUserMessageAt: text(summary?.lastUserMessageAt) || null,
        updatedAt: text(summary?.lastUserMessageAt) || session.updatedAt
      };
    })
    .sort((left: any, right: any) => String(right.updatedAt).localeCompare(String(left.updatedAt)));
  const warnings = kafkaSummary?.warning ? [kafkaSummary.warning] : [];
  return json(200, { ok: true, status: "ready", sessions, count: sessions.length, warnings, mode: options.mode ?? "native-test" });
}
async function nativeSessionDetail(options: { snapshot?: () => Promise<any>; mode?: WorkbenchMode }, sessionId: string, messagesOnly: boolean) {
  const state = await requiredSnapshot(options);
  const session = state.sessions[sessionId];
  if (!session) return json(404, { ok: false, error: { code: "session_not_found", message: "native session was not found" } });
  return json(200, messagesOnly ? { ok: true, sessionId, messages: session.messages ?? [], count: Array.isArray(session.messages) ? session.messages.length : 0, mode: options.mode ?? "native-test" } : { ok: true, session, mode: options.mode ?? "native-test" });
}
async function nativeTurn(options: { snapshot?: () => Promise<any>; mode?: WorkbenchMode }, traceId: string) {
  const state = await requiredSnapshot(options);
  const turn = state.turns[traceId];
  return turn ? json(200, { ok: true, ...turn, mode: options.mode ?? "native-test" }) : json(404, { ok: false, error: { code: "turn_not_found", message: "native turn was not found" } });
}
async function requiredSnapshot(options: { snapshot?: () => Promise<any> }) { if (!options.snapshot) throw Object.assign(new Error("native projection is unavailable"), { code: "native_projection_unavailable" }); return options.snapshot(); }
async function nativeEventStream(options: { mode?: WorkbenchMode; kafkaEventBridge?: any }, url: URL) {
  const mode = options.mode ?? "native-test";
  const bridge = options.kafkaEventBridge;
  const sessionId = text(url.searchParams.get("sessionId"));
  const traceId = text(url.searchParams.get("traceId"));
  if (!sessionId && !traceId) return json(400, { ok: false, error: { code: "workbench_realtime_scope_required", message: "sessionId or traceId is required" } });
  if (mode === "agentrun-native" && (bridge?.capabilities?.liveKafkaSse !== true || typeof bridge?.subscribeLiveHwlabEvents !== "function")) {
    return json(503, { ok: false, error: { code: "workbench_live_kafka_unconfigured", message: "Native Workbench requires the Kafka SSE bridge" } });
  }
  if (mode === "agentrun-native" && bridge?.capabilities?.kafkaRefreshReplay === true) {
    return nativeKafkaRefreshEventStream(bridge, sessionId, traceId, mode);
  }
  const encoder = new TextEncoder();
  let heartbeat: ReturnType<typeof setInterval> | undefined;
  let unsubscribe: (() => void) | undefined;
  let closed = false;
  return new Response(new ReadableStream({
    start(controller) {
      const write = (name: string, payload: unknown) => { if (!closed) controller.enqueue(encoder.encode(`event: ${name}\ndata: ${JSON.stringify(payload)}\n\n`)); };
      write("workbench.connected", { type: "connected", status: "connected", mode, realtimeSource: mode === "agentrun-native" ? "hwlab.event.v1" : "fixture", deliverySemantics: "live-only", liveOnly: true, replay: false, filters: { sessionId: sessionId || null, traceId: traceId || null }, valuesPrinted: false });
      if (typeof bridge?.subscribeLiveHwlabEvents === "function") unsubscribe = bridge.subscribeLiveHwlabEvents((envelope: any) => {
        if (!nativeEnvelopeMatches(envelope, sessionId, traceId)) return;
        write("hwlab.event.v1", envelope);
      });
      void Promise.resolve(bridge?.liveReady ?? bridge?.ready).catch((error: any) => {
        write("workbench.error", { type: "error", status: "blocked", error: { code: error?.code ?? "workbench_live_kafka_start_failed", message: error?.message ?? String(error) }, realtimeSource: "hwlab.event.v1", valuesRedacted: true });
        closed = true;
        if (heartbeat) clearInterval(heartbeat);
        unsubscribe?.();
        controller.close();
      });
      heartbeat = setInterval(() => controller.enqueue(encoder.encode(`: native-test-heartbeat ${Date.now()}\n\n`)), 10_000);
    },
    cancel() { closed = true; if (heartbeat) clearInterval(heartbeat); unsubscribe?.(); }
  }), { status: 200, headers: { "content-type": "text/event-stream", "cache-control": "no-store", connection: "keep-alive" } });
}

function nativeKafkaRefreshEventStream(bridge: any, sessionId: string, traceId: string, mode: WorkbenchMode) {
  const refreshReplay = bridge?.refreshReplay;
  if (!refreshReplay || typeof bridge?.queryHwlabEventRetention !== "function") {
    return json(503, { ok: false, error: { code: "workbench_kafka_refresh_unconfigured", message: "Native Workbench Kafka refresh replay requires its retention query runtime" } });
  }
  const encoder = new TextEncoder();
  let heartbeat: ReturnType<typeof setInterval> | undefined;
  let handoff: ReturnType<typeof createWorkbenchKafkaRefreshHandoff> | undefined;
  let closed = false;
  return new Response(new ReadableStream({
    start(controller) {
      const write = (name: string, payload: unknown) => {
        if (closed) return false;
        controller.enqueue(encoder.encode(`event: ${name}\ndata: ${JSON.stringify(payload)}\n\n`));
        return true;
      };
      handoff = createWorkbenchKafkaRefreshHandoff({
        sessionId,
        traceId,
        liveBufferLimit: refreshReplay.liveBufferLimit,
        identityWindowLimit: refreshReplay.matchedEventLimit + refreshReplay.liveBufferLimit,
        subscribeLive: (listener: (envelope: any, transport: any) => void) => bridge.subscribeLiveHwlabEvents(listener),
        queryRetention: async ({ signal }: { signal: AbortSignal }) => {
          const result = await bridge.queryHwlabEventRetention({
            sessionId,
            traceId,
            limit: refreshReplay.matchedEventLimit,
            scanLimit: refreshReplay.scanLimit,
            timeoutMs: refreshReplay.timeoutMs,
            groupIdPrefix: refreshReplay.groupIdPrefix,
            partitionKey: sessionId,
            fromBeginning: true,
            signal
          });
          console.log(JSON.stringify({
            code: "workbench-kafka-refresh-query",
            component: "hwlab-workbench-native",
            sessionId: sessionId || null,
            traceId: traceId || null,
            completionReason: result?.completionReason ?? null,
            complete: result?.completion?.complete === true,
            scannedCount: result?.scannedCount ?? null,
            parsedCount: result?.parsedCount ?? null,
            matchedCount: result?.matchedCount ?? null,
            filterRejectedCount: result?.filterRejectedCount ?? null,
            keyRejectedCount: result?.keyRejectedCount ?? null,
            partitionKeyScoped: result?.partitionKeyScoped === true,
            targetPartition: result?.targetPartition ?? null,
            index: result?.index ?? null,
            timing: result?.timing ?? null,
            valuesPrinted: false
          }));
          return result;
        },
        deliverEvent: (envelope: any) => write("hwlab.event.v1", envelope),
        deliverConnected: (summary: any) => write("workbench.connected", {
          type: "connected",
          status: "connected",
          mode,
          capabilities: bridge.capabilities,
          realtimeSource: "hwlab.event.v1",
          deliverySemantics: "kafka-retention-then-live",
          liveOnly: false,
          replay: true,
          replaySupported: true,
          lossPossible: false,
          filters: { sessionId: sessionId || null, traceId: traceId || null },
          refreshReplay: summary,
          valuesPrinted: false
        }),
        onFailure: (error: any) => {
          if (closed) return;
          write("workbench.error", workbenchKafkaRefreshErrorPayload(error, { sessionId, traceId }));
          closed = true;
          if (heartbeat) clearInterval(heartbeat);
          controller.close();
        }
      });
      void Promise.resolve(bridge.liveReady ?? bridge.ready)
        .then(() => handoff?.start())
        .then(() => {
          if (closed) return;
          heartbeat = setInterval(() => write("workbench.heartbeat", {
            type: "heartbeat",
            status: "connected",
            realtimeSource: "hwlab.event.v1",
            deliverySemantics: "kafka-retention-then-live",
            liveOnly: false,
            replay: true,
            lossPossible: false,
            serverSentAt: new Date().toISOString(),
            valuesPrinted: false
          }), 10_000);
        })
        .catch((error: any) => {
          if (closed) return;
          write("workbench.error", workbenchKafkaRefreshErrorPayload(error, { sessionId, traceId }));
          closed = true;
          controller.close();
        });
    },
    cancel() {
      closed = true;
      if (heartbeat) clearInterval(heartbeat);
      handoff?.stop("connection-closed");
    }
  }), { status: 200, headers: { "content-type": "text/event-stream", "cache-control": "no-store", connection: "keep-alive" } });
}

function nativeEnvelopeMatches(envelope: any, sessionId: string, traceId: string) {
  const event = envelope?.event && typeof envelope.event === "object" ? envelope.event : {};
  const envelopeSessionId = text(envelope?.sessionId ?? envelope?.hwlabSessionId ?? event.sessionId);
  const envelopeTraceId = text(envelope?.traceId ?? event.traceId);
  return (!sessionId || envelopeSessionId === sessionId) && (!traceId || envelopeTraceId === traceId);
}

function firstUserMessagePreview(messages: unknown): string | null {
  if (!Array.isArray(messages)) return null;
  for (const message of messages) {
    if (!message || typeof message !== "object" || text((message as any).role) !== "user") continue;
    const preview = text((message as any).text ?? (message as any).content ?? (message as any).message);
    if (preview) return preview.slice(0, 240);
  }
  return null;
}

function actorFrom(request: Request, nativeTest: boolean) {
  const id = text(request.headers.get("x-hwlab-actor-id"));
  if (!id && nativeTest) return { id: "usr_native", role: "user" };
  if (!id) throw Object.assign(new Error("x-hwlab-actor-id is required"), { code: "auth_required" });
  return { id, role: text(request.headers.get("x-hwlab-actor-role")) || "user" };
}
function resultResponse(result: any, successStatus = 200) { return json(result?.ok === true ? successStatus : statusFor(result?.error?.code), result); }
function legacyResponse(result: any, successStatus = 200) { return result?.ok === true ? json(successStatus, { ok: true, ...result.data, orchestrationMode: result.mode }) : resultResponse(result, successStatus); }
function statusFor(code: unknown) { const value = String(code ?? ""); if (value === "auth_required") return 401; if (value.includes("not_found")) return 404; if (value.includes("required") || value === "invalid_input") return 400; return 503; }
async function bodyObject(request: Request) { const body = await request.json().catch(() => null); if (!body || typeof body !== "object" || Array.isArray(body)) throw Object.assign(new Error("JSON object body is required"), { code: "invalid_json" }); return body as Record<string, unknown>; }
function text(value: unknown) { return String(value ?? "").trim(); }
function json(status: number, body: unknown) { return Response.json(body, { status, headers: { "cache-control": "no-store" } }); }
