import { describe, expect, vi } from "vitest";

// The SpanPresenter module graph imports `~/v3/runStore.server`, which imports `~/db.server`
// at load (and a large transitive graph: runEngine, eventRepository, mollifier, ...). We stub the
// two boundaries the presenter reads through so the file loads under test, then drive it entirely
// against real Postgres containers — NEVER mocking a DB client.
//
//  * `~/db.server` — the module-level `prisma`/`$replica` exports. The presenter receives its
//    control-plane handle through the BasePresenter constructor (`new SpanPresenter(cp, cp)`), so
//    these stubs are never read on the path under test.
//  * `~/v3/runStore.server` — the run-ops store singleton. This is the ONE wiring boundary we
//    override: the test injects a routing-shaped store (a RoutingRunStore over the two-DB hetero
//    fixture) in its place. This is a wiring override, not a DB mock — every run-ops read still
//    executes against a real container.
vi.mock("~/db.server", () => ({
  prisma: {},
  $replica: {},
}));

const routingStoreRef = vi.hoisted(() => ({ current: undefined as unknown }));
vi.mock("~/v3/runStore.server", () => ({
  get runStore() {
    return routingStoreRef.current;
  },
}));

import { PostgresRunStore, RoutingRunStore } from "@internal/run-store";
import type { RunStore } from "@internal/run-store";
import { heteroPostgresTest } from "@internal/testcontainers";
import type { Prisma, PrismaClient } from "@trigger.dev/database";
import { SpanPresenter } from "~/presenters/v3/SpanPresenter.server";

vi.setConfig({ testTimeout: 90_000 });

// 25-char internal id → cuid → LEGACY; v1 internal id (26 chars, version "1" at index 25) → NEW (the residency
// classifier shared with the RoutingRunStore's default `ownerEngine`).
const CUID_25 = "c".repeat(25);
const NEW_ID_26 = "k".repeat(24) + "01";

type SeedContext = {
  organizationId: string;
  projectId: string;
  environmentId: string;
};

async function seedParents(prisma: PrismaClient, slug: string): Promise<SeedContext> {
  const organization = await prisma.organization.create({
    data: { title: `org-${slug}`, slug: `org-${slug}` },
  });
  const project = await prisma.project.create({
    data: {
      name: `proj-${slug}`,
      slug: `proj-${slug}`,
      organizationId: organization.id,
      externalRef: `proj-${slug}`,
    },
  });
  const runtimeEnvironment = await prisma.runtimeEnvironment.create({
    data: {
      slug: `env-${slug}`,
      type: "PRODUCTION",
      projectId: project.id,
      organizationId: organization.id,
      apiKey: `tr_prod_${slug}`,
      pkApiKey: `pk_prod_${slug}`,
      shortcode: `sc-${slug}`,
    },
  });

  return {
    organizationId: organization.id,
    projectId: project.id,
    environmentId: runtimeEnvironment.id,
  };
}

/** Mirror the org/project/env parents onto a second DB with the SAME ids (TaskRun FKs need them
 *  on every DB a run is hydrated from). */
async function mirrorParents(prisma: PrismaClient, ctx: SeedContext, slug: string): Promise<void> {
  await prisma.organization.create({
    data: { id: ctx.organizationId, title: `org-${slug}`, slug: `org-${slug}` },
  });
  await prisma.project.create({
    data: {
      id: ctx.projectId,
      name: `proj-${slug}`,
      slug: `proj-${slug}`,
      organizationId: ctx.organizationId,
      externalRef: `proj-${slug}`,
    },
  });
  await prisma.runtimeEnvironment.create({
    data: {
      id: ctx.environmentId,
      slug: `env-${slug}`,
      type: "PRODUCTION",
      projectId: ctx.projectId,
      organizationId: ctx.organizationId,
      apiKey: `tr_prod_${slug}_b`,
      pkApiKey: `pk_prod_${slug}_b`,
      shortcode: `sc-${slug}-b`,
    },
  });
}

async function createRun(
  prisma: PrismaClient,
  ctx: SeedContext,
  run: {
    id: string;
    friendlyId: string;
    spanId: string;
    parentSpanId?: string;
    taskIdentifier?: string;
    status?: Prisma.TaskRunCreateInput["status"];
    parentTaskRunId?: string;
    rootTaskRunId?: string;
  }
) {
  return prisma.taskRun.create({
    data: {
      id: run.id,
      friendlyId: run.friendlyId,
      taskIdentifier: run.taskIdentifier ?? "my-task",
      status: run.status ?? "COMPLETED_SUCCESSFULLY",
      payload: JSON.stringify({ foo: run.friendlyId }),
      payloadType: "application/json",
      traceId: `trace_${run.friendlyId}`,
      spanId: run.spanId,
      parentSpanId: run.parentSpanId,
      parentTaskRunId: run.parentTaskRunId,
      rootTaskRunId: run.rootTaskRunId,
      queue: "task/my-task",
      runTags: ["alpha", "beta"],
      runtimeEnvironmentId: ctx.environmentId,
      projectId: ctx.projectId,
      organizationId: ctx.organizationId,
      environmentType: "PRODUCTION",
      engine: "V2",
      taskEventStore: "taskEvent",
    },
  });
}

/**
 * Test-only wiring shim. In production the run-ops store's DB selection is the store's own
 * concern, but `SpanPresenter` still passes `this._replica`/`this._prisma` (the control-plane
 * handle) as the explicit `client` arg to `runStore.findRun`/`findRuns`. `PostgresRunStore`
 * honours an explicit client (`client ?? this.readOnlyPrisma`), so without this shim a run-ops
 * read would execute against the control-plane DB. Reconciling that explicit-client override
 * with split routing is the job of the runStore.server.ts wiring seam, explicitly OUT of this
 * unit's scope. The shim represents that reconciliation: it drops the
 * presenter's client arg so each underlying PostgresRunStore reads from its OWN bound DB — the
 * residency-routed behaviour the presenter will inherit once the seam is wired. It fakes ONLY the
 * client wiring; every DB read still hits a real container.
 */
function ownDbStore(prisma: PrismaClient): RunStore {
  const inner = new PostgresRunStore({ prisma, readOnlyPrisma: prisma });
  return new Proxy(inner, {
    get(target, prop) {
      if (prop === "findRun" || prop === "findRuns") {
        return (...args: unknown[]) => {
          // Strip a trailing explicit `client` arg so the store reads from its own DB.
          const stripped = stripTrailingClient(prop, args);
          return (target[prop] as (...a: unknown[]) => unknown).apply(target, stripped);
        };
      }
      const value = Reflect.get(target, prop, target);
      return typeof value === "function" ? value.bind(target) : value;
    },
  }) as unknown as RunStore;
}

function stripTrailingClient(method: "findRun" | "findRuns", args: unknown[]): unknown[] {
  // findRun(where, argsOrClient?, client?) ; findRuns(args, client?). The last arg is the
  // presenter's explicit client when it is not a projection object.
  const last = args[args.length - 1] as { select?: unknown; include?: unknown } | undefined;
  const isProjection =
    typeof last === "object" && last !== null && ("select" in last || "include" in last);
  if (args.length === 0 || isProjection) {
    return args;
  }
  return args.slice(0, -1);
}

/** A read-only client wrapper: throws on any write, asserting the legacy slot is replica-only. */
function asReplica(prisma: PrismaClient): PrismaClient {
  return new Proxy(prisma, {
    get(target, prop, receiver) {
      if (prop === "taskRun") {
        return new Proxy((target as any).taskRun, {
          get(trTarget, trProp) {
            if (
              ["create", "update", "updateMany", "upsert", "delete", "deleteMany"].includes(
                String(trProp)
              )
            ) {
              return () => {
                throw new Error(`legacy slot is read-replica-only; ${String(trProp)} is forbidden`);
              };
            }
            return (trTarget as any)[trProp];
          },
        });
      }
      return Reflect.get(target, prop, receiver);
    },
  }) as unknown as PrismaClient;
}

describe("SpanPresenter run-ops/control-plane partition (legacy + new)", () => {
  // Span detail resolves run + children through the run-ops store, region/schedule/session
  // on control-plane, no cross-DB join.
  heteroPostgresTest(
    "findRun hydrates the run through the run-ops store (new-first) and the children-by-parentSpanId set; region/schedule/session resolve from the control-plane client",
    async ({ prisma14, prisma17 }) => {
      // prisma17 = NEW run-ops; prisma14 = LEGACY run-ops replica AND, for this partition proof,
      // the control-plane DB (a physically distinct DB from the NEW run-ops store).
      const cp = prisma14;

      // Seed the env/project/org parents on BOTH run-ops DBs (FKs) and on the CP DB.
      const ctxNew = await seedParents(prisma17, "partn");
      await mirrorParents(prisma14, ctxNew, "partn"); // legacy run-ops + CP parents share ids

      const runId = `run_${NEW_ID_26}`; // run-ops id → NEW residency
      const childMigratedId = `run_a${NEW_ID_26.slice(1)}`; // also NEW
      const parentFriendlyId = `run_p${NEW_ID_26.slice(1)}`; // v1 body → routes NEW by friendlyId
      await createRun(prisma17, ctxNew, {
        id: runId,
        friendlyId: parentFriendlyId,
        spanId: "span_parent",
        taskIdentifier: "parent-task",
      });
      // A child whose parentSpanId points at the parent's span — lives on NEW.
      await createRun(prisma17, ctxNew, {
        id: childMigratedId,
        friendlyId: "run_child_new",
        spanId: "span_child_new",
        parentSpanId: "span_parent",
        parentTaskRunId: runId,
        taskIdentifier: "child-task",
      });

      // Control-plane rows live on the CP DB only.
      const workerGroup = await cp.workerInstanceGroup.create({
        data: {
          name: "us-east-1-group",
          location: "N. Virginia, USA",
          masterQueue: "main",
          type: "MANAGED",
          token: { create: { tokenHash: `tok-${ctxNew.projectId}` } },
        },
      });
      const schedule = await cp.taskSchedule.create({
        data: {
          friendlyId: "sched_1234",
          taskIdentifier: "parent-task",
          projectId: ctxNew.projectId,
          deduplicationKey: "dedup-1",
          type: "DECLARATIVE",
          generatorExpression: "0 * * * *",
          generatorDescription: "every hour",
          timezone: "UTC",
        },
      });

      routingStoreRef.current = new RoutingRunStore({
        new: ownDbStore(prisma17),
        legacy: ownDbStore(prisma14),
      });

      const presenter = new SpanPresenter(cp, cp);

      // (a) run hydrated through the run-ops store (NEW), byte-identical to the source row incl.
      //     the run-ops self-relations.
      const run = await presenter.findRun({
        originalRunId: parentFriendlyId,
        spanId: "span_parent",
        environmentId: ctxNew.environmentId,
      });
      expect(run?.id).toBe(runId);
      expect(run?.friendlyId).toBe(parentFriendlyId);
      expect(run?.taskIdentifier).toBe("parent-task");
      expect(run?.runTags).toEqual(["alpha", "beta"]);
      // Nested run-ops self-relation resolved on the same (NEW) store.
      expect(run?.parentTaskRun).toBeNull();

      // (b) the run does NOT exist on the CP DB — the run-ops read could only have come from the
      //     run-ops store, never a CP join.
      expect(await cp.taskRun.findFirst({ where: { friendlyId: parentFriendlyId } })).toBeNull();

      // (c) the control-plane standalone reads resolve from the CP client.
      const region = await cp.workerInstanceGroup.findFirst({ where: { masterQueue: "main" } });
      expect(region?.name).toBe(workerGroup.name);
      expect(await presenter.resolveSchedule(schedule.id)).toMatchObject({
        friendlyId: "sched_1234",
        timezone: "UTC",
      });
    }
  );

  // Children set served by runStore.findRuns through the routing store.
  heteroPostgresTest(
    "triggeredRuns (children-by-parentSpanId) is served by runStore.findRuns with the presenter's exact select",
    async ({ prisma14, prisma17 }) => {
      const ctx = await seedParents(prisma17, "kids");

      await createRun(prisma17, ctx, {
        id: `run_${NEW_ID_26}`,
        friendlyId: "run_parent2",
        spanId: "span_p2",
      });
      await createRun(prisma17, ctx, {
        id: `run_b${NEW_ID_26.slice(1)}`,
        friendlyId: "run_kid_a",
        spanId: "span_kid_a",
        parentSpanId: "span_p2",
      });
      await createRun(prisma17, ctx, {
        id: `run_c${NEW_ID_26.slice(1)}`,
        friendlyId: "run_kid_b",
        spanId: "span_kid_b",
        parentSpanId: "span_p2",
      });

      const store = new RoutingRunStore({
        new: ownDbStore(prisma17),
        legacy: ownDbStore(prisma14),
      });

      const triggeredRuns = await store.findRuns({
        where: { parentSpanId: "span_p2" },
        select: {
          friendlyId: true,
          taskIdentifier: true,
          spanId: true,
          createdAt: true,
          status: true,
        },
      });

      expect(triggeredRuns.map((r) => r.friendlyId).sort()).toEqual(["run_kid_a", "run_kid_b"]);
      // select projection holds: no `id`/`payload` leaked through.
      expect(triggeredRuns[0]).not.toHaveProperty("id");
      expect(triggeredRuns[0]).not.toHaveProperty("payload");
    }
  );

  // Old in-retention run served from the legacy replica, never the primary.
  heteroPostgresTest(
    "a legacy-residency run resolves through the store's LEGACY slot, which exposes only a replica handle",
    async ({ prisma14, prisma17 }) => {
      const ctx = await seedParents(prisma14, "legacy");

      const legacyRunId = `run_${CUID_25}`; // cuid → LEGACY residency
      await createRun(prisma14, ctx, {
        id: legacyRunId,
        friendlyId: "run_legacy",
        spanId: "span_legacy",
        taskIdentifier: "legacy-task",
      });

      // The LEGACY slot is wired over a replica (read-only) handle; the NEW slot over the new DB.
      const store = new RoutingRunStore({
        new: ownDbStore(prisma17),
        legacy: ownDbStore(asReplica(prisma14)),
      });

      // Routed by `id` residency (cuid → LEGACY). The presenter's findRun keys by friendlyId/spanId
      // (which route NEW-default through the store today); routing by id is the store-level proof
      // that the LEGACY slot serves in-retention runs. The legacy slot's replica handle forbids
      // writes — proving the read route can never touch a legacy writer.
      const found = await store.findRun(
        { id: legacyRunId },
        { select: { id: true, friendlyId: true, taskIdentifier: true } }
      );
      expect(found?.id).toBe(legacyRunId);
      expect(found?.taskIdentifier).toBe("legacy-task");
    }
  );

  // A known-migrated run is not re-probed on legacy.
  heteroPostgresTest(
    "a NEW-residency id is served by the NEW slot and the LEGACY slot is never invoked",
    async ({ prisma14, prisma17 }) => {
      const ctx = await seedParents(prisma17, "knownmig");

      const newRunId = `run_${NEW_ID_26}`; // run-ops id → NEW residency
      await createRun(prisma17, ctx, {
        id: newRunId,
        friendlyId: "run_known_new",
        spanId: "span_known_new",
        taskIdentifier: "new-task",
      });

      // LEGACY slot throws on ANY read — asserting the residency short-circuit never probes it.
      const legacyThrows = new Proxy({} as RunStore, {
        get(_t, prop) {
          if (prop === "findRun" || prop === "findRuns") {
            return () => {
              throw new Error(`LEGACY slot must not be probed for a NEW id (${String(prop)})`);
            };
          }
          return undefined;
        },
      });

      const store = new RoutingRunStore({
        new: ownDbStore(prisma17),
        legacy: legacyThrows,
      });

      const found = await store.findRun(
        { id: newRunId },
        { select: { id: true, taskIdentifier: true } }
      );
      expect(found?.id).toBe(newRunId);
      expect(found?.taskIdentifier).toBe("new-task");
    }
  );

  // Passthrough (single-DB): NEW and LEGACY slots are the same store over one client.
  heteroPostgresTest(
    "single-DB collapses both slots to one PostgresRunStore; the presenter resolves run + children + control-plane from the one client",
    async ({ prisma14 }) => {
      const cp = prisma14;
      const ctx = await seedParents(prisma14, "passthru");

      const runId = `run_${NEW_ID_26}`;
      await createRun(prisma14, ctx, {
        id: runId,
        friendlyId: "run_solo",
        spanId: "span_solo",
        taskIdentifier: "solo-task",
      });
      await createRun(prisma14, ctx, {
        id: `run_d${NEW_ID_26.slice(1)}`,
        friendlyId: "run_solo_kid",
        spanId: "span_solo_kid",
        parentSpanId: "span_solo",
      });
      const schedule = await cp.taskSchedule.create({
        data: {
          friendlyId: "sched_solo",
          taskIdentifier: "solo-task",
          projectId: ctx.projectId,
          deduplicationKey: "dedup-solo",
          type: "DECLARATIVE",
          generatorExpression: "0 * * * *",
          generatorDescription: "every hour",
          timezone: "UTC",
        },
      });

      // Both slots are the same store over the one client — the single-DB collapse.
      const solo = ownDbStore(prisma14);
      routingStoreRef.current = new RoutingRunStore({ new: solo, legacy: solo });

      const presenter = new SpanPresenter(cp, cp);

      const run = await presenter.findRun({
        originalRunId: "run_solo",
        spanId: "span_solo",
        environmentId: ctx.environmentId,
      });
      expect(run?.id).toBe(runId);
      expect(run?.taskIdentifier).toBe("solo-task");

      // Children resolve from the same single store.
      const children = await (routingStoreRef.current as RunStore).findRuns({
        where: { parentSpanId: "span_solo" },
        select: {
          friendlyId: true,
          taskIdentifier: true,
          spanId: true,
          createdAt: true,
          status: true,
        },
      });
      expect(children.map((c) => c.friendlyId)).toEqual(["run_solo_kid"]);

      // Control-plane read from the same single client.
      expect(await presenter.resolveSchedule(schedule.id)).toMatchObject({
        friendlyId: "sched_solo",
      });
    }
  );

  // Cross-seam tree shape: parent on LEGACY (in-retention), child on NEW (born-new).
  heteroPostgresTest(
    "parent run on the legacy replica, child run on new — relations resolve across the seam, no cross-DB join",
    async ({ prisma14, prisma17 }) => {
      const ctx = await seedParents(prisma14, "e2e4");
      await mirrorParents(prisma17, ctx, "e2e4");

      const parentId = `run_${CUID_25}`; // cuid → LEGACY (in-retention)
      const childId = `run_${NEW_ID_26}`; // run-ops id → NEW (born-new)

      await createRun(prisma14, ctx, {
        id: parentId,
        friendlyId: "run_e2e_parent",
        spanId: "span_e2e_parent",
        taskIdentifier: "parent",
        rootTaskRunId: parentId,
      });
      // The child lives on NEW; it links to the parent across the seam ONLY by `parentSpanId`
      // (a plain indexed column — the exact key `triggeredRuns` uses), NOT by a cross-DB FK
      // (`parentTaskRunId`/`rootTaskRunId` would violate the FK since the parent is on LEGACY;
      // a tree's FK self-relations stay single-DB).
      await createRun(prisma17, ctx, {
        id: childId,
        friendlyId: "run_e2e_child",
        spanId: "span_e2e_child",
        parentSpanId: "span_e2e_parent",
        taskIdentifier: "child",
      });

      const store = new RoutingRunStore({
        new: ownDbStore(prisma17),
        legacy: ownDbStore(asReplica(prisma14)),
      });

      // The parent resolves from the LEGACY slot (routed by its cuid id).
      const parent = await store.findRun(
        { id: parentId },
        {
          select: {
            id: true,
            friendlyId: true,
            rootTaskRun: { select: { friendlyId: true } },
          },
        }
      );
      expect(parent?.id).toBe(parentId);
      // Run-ops self-relation (rootTaskRun) resolves on the parent's own (LEGACY) store — a
      // tree's FK self-relations stay single-DB.
      expect(parent?.rootTaskRun?.friendlyId).toBe("run_e2e_parent");

      // The child resolves from the NEW slot (routed by its run-ops id) and points back at the parent
      // span — the cross-the-line parent/child shape, with no cross-DB join.
      const child = await store.findRun(
        { id: childId },
        { select: { id: true, parentSpanId: true, friendlyId: true } }
      );
      expect(child?.id).toBe(childId);
      expect(child?.parentSpanId).toBe("span_e2e_parent");
    }
  );
});
