import { json } from "@remix-run/server-runtime";
import { tryCatch } from "@trigger.dev/core/utils";
import { nanoid } from "nanoid";
import { z } from "zod";
import { $replica, prisma } from "~/db.server";
import { getRealtimeStreamInstance } from "~/services/realtime/v1StreamsGlobal.server";
import { createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server";
import { ServiceValidationError } from "~/v3/services/common.server";
import { runStore } from "~/v3/runStore.server";

const ParamsSchema = z.object({
  runId: z.string(),
  target: z.enum(["self", "parent", "root"]),
  streamId: z.string(),
});

// S2 enforces a 1 MiB per-record limit (metered as
// `8 + 2*H + Σ(header name+value) + body`). Cap the raw HTTP body at
// 512 KiB so the JSON wrapper, string escaping, and any future per-record
// header additions all stay well under S2's ceiling.
// See https://s2.dev/docs/limits.
const MAX_APPEND_BODY_BYTES = 1024 * 512;

const { action } = createActionApiRoute(
  {
    params: ParamsSchema,
    maxContentLength: MAX_APPEND_BODY_BYTES,
  },
  async ({ request, params, authentication }) => {
    const run = await runStore.findRun(
      {
        friendlyId: params.runId,
        runtimeEnvironmentId: authentication.environment.id,
      },
      {
        select: {
          id: true,
          friendlyId: true,
          parentTaskRun: {
            select: {
              friendlyId: true,
            },
          },
          rootTaskRun: {
            select: {
              friendlyId: true,
            },
          },
        },
      },
      $replica
    );

    if (!run) {
      return new Response("Run not found", { status: 404 });
    }

    const targetId =
      params.target === "self"
        ? run.friendlyId
        : params.target === "parent"
          ? run.parentTaskRun?.friendlyId
          : run.rootTaskRun?.friendlyId;

    if (!targetId) {
      return new Response("Target not found", { status: 404 });
    }

    const targetRun = await runStore.findRun(
      {
        friendlyId: targetId,
        runtimeEnvironmentId: authentication.environment.id,
      },
      {
        select: {
          realtimeStreams: true,
          realtimeStreamsVersion: true,
          completedAt: true,
          id: true,
          streamBasinName: true,
        },
      },
      prisma
    );

    if (!targetRun) {
      return new Response("Run not found", { status: 404 });
    }

    if (targetRun.completedAt) {
      return new Response("Cannot append to a realtime stream on a completed run", {
        status: 400,
      });
    }

    if (!targetRun.realtimeStreams.includes(params.streamId)) {
      await runStore.pushRealtimeStream(targetRun.id, params.streamId, prisma);
    }

    const part = await request.text();

    const realtimeStream = getRealtimeStreamInstance(
      authentication.environment,
      targetRun.realtimeStreamsVersion,
      { run: targetRun }
    );

    const partId = request.headers.get("X-Part-Id") ?? nanoid(7);

    const [appendError] = await tryCatch(
      realtimeStream.appendPart(part, partId, targetId, params.streamId)
    );

    if (appendError) {
      if (appendError instanceof ServiceValidationError) {
        return json(
          {
            ok: false,
            error: appendError.message,
          },
          { status: appendError.status ?? 422 }
        );
      } else {
        return json(
          {
            ok: false,
            error: appendError.message,
          },
          { status: 500 }
        );
      }
    }

    return json(
      {
        ok: true,
      },
      { status: 200 }
    );
  }
);

export { action };
