import { json } from "@remix-run/server-runtime";
import { z } from "zod";
import { $replica } from "~/db.server";
import { S2RealtimeStreams } from "~/services/realtime/s2realtimeStreams.server";
import {
  canonicalSessionAddressingKey,
  isSessionFriendlyIdForm,
  resolveSessionByIdOrExternalId,
} from "~/services/realtime/sessions.server";
import { getRealtimeStreamInstance } from "~/services/realtime/v1StreamsGlobal.server";
import { anyResource, createLoaderApiRoute } from "~/services/routeBuilders/apiBuilder.server";

const ParamsSchema = z.object({
  session: z.string(),
  io: z.enum(["out", "in"]),
});

const SearchSchema = z.object({
  // S2 sequence number — same cursor format as the SSE Last-Event-ID
  // (the SSE `id:` field on session-channel events is the seq_num,
  // stringified). Records returned have `seqNum > afterEventId`.
  afterEventId: z.string().regex(/^\d+$/).optional(),
});

// GET: non-SSE, `wait=0` drain of a session channel. Returns a JSON body
// `{ records: StreamRecord[] }` with whatever records exist after
// `afterEventId` (or from the head if absent) and closes immediately.
//
// Used by the SDK's `replaySessionOutTail` at run boot — the SSE long-poll
// path costs ~1s per fresh chat (the timeout duration) regardless of stream
// content, which is unacceptable on the first-message TTFC budget. This
// route gives the agent a cheap "what's there right now" peek instead.
//
// Same row-optional addressing as the SSE GET route in `…$io.ts`: we
// resolve via `resolveSessionByIdOrExternalId` and only 404 for opaque
// `session_*` friendlyIds (which must reference a real row). External-id
// form falls through with `row: null` so the boot path doesn't 404 on a
// fresh chat that hasn't written its first chunk yet.
export const loader = createLoaderApiRoute(
  {
    params: ParamsSchema,
    searchParams: SearchSchema,
    allowJWT: true,
    corsStrategy: "all",
    findResource: async (params, auth) => {
      const row = await resolveSessionByIdOrExternalId(
        $replica,
        auth.environment.id,
        params.session
      );
      if (!row && isSessionFriendlyIdForm(params.session)) {
        return undefined;
      }
      return {
        row,
        addressingKey: canonicalSessionAddressingKey(row, params.session),
      };
    },
    authorization: {
      action: "read",
      // Multi-key: the channel is addressable by the URL key, the row's
      // friendlyId, and (if set) externalId. Type-level `read:sessions`
      // matches any of them; `read:all` / `admin` bypass via the JWT
      // ability's wildcard branches.
      resource: ({ row, addressingKey }) => {
        const ids = new Set<string>([addressingKey]);
        if (row) {
          ids.add(row.friendlyId);
          if (row.externalId) ids.add(row.externalId);
        }
        return anyResource([...ids].map((id) => ({ type: "sessions", id })));
      },
    },
  },
  async ({ params, authentication, resource, searchParams }) => {
    const realtimeStream = getRealtimeStreamInstance(authentication.environment, "v2", {
      session: resource.row,
      organization: resource.row ? null : authentication.environment.organization,
    });

    if (!(realtimeStream instanceof S2RealtimeStreams)) {
      return new Response("Session channels require the S2 realtime backend", {
        status: 501,
      });
    }

    const afterSeqNum =
      searchParams.afterEventId !== undefined ? Number(searchParams.afterEventId) : undefined;

    const records = await realtimeStream.readSessionStreamRecords(
      resource.addressingKey,
      params.io,
      afterSeqNum
    );

    return json({ records });
  }
);
