import { CURRENT_API_VERSION } from "~/api/versions";
import {
  NativeRealtimeClient,
  type RealtimeListEnvironment,
} from "~/services/realtime/nativeRealtimeClient.server";
import { type RealtimeRunRow } from "~/services/realtime/electricStreamProtocol.server";
import { EnvChangeRouter } from "~/services/realtime/envChangeRouter.server";
import { describe, expect, it } from "vitest";

function sampleRow(): RealtimeRunRow {
  return {
    id: "run_1",
    taskIdentifier: "t",
    createdAt: new Date("2026-06-07T10:00:00.000Z"),
    updatedAt: new Date("2026-06-07T10:00:01.000Z"),
    startedAt: null,
    delayUntil: null,
    queuedAt: null,
    expiredAt: null,
    completedAt: null,
    friendlyId: "run_friendly_1",
    number: 1,
    isTest: false,
    status: "EXECUTING",
    usageDurationMs: 0,
    costInCents: 0,
    baseCostInCents: 0,
    ttl: null,
    payload: "{}",
    payloadType: "application/json",
    metadata: null,
    metadataType: "application/json",
    output: null,
    outputType: "application/json",
    runTags: [],
    error: null,
    realtimeStreams: [],
  };
}

// Only the initial-snapshot path is exercised here, which touches the shared
// #buildResponse — enough to lock the response-header contract.
function makeClient(row: RealtimeRunRow | null) {
  return new NativeRealtimeClient({
    runReader: {
      getRunById: async () => row,
      hydrateByIds: async () => (row ? [row] : []),
    } as any,
    runListResolver: { resolveMatchingRunIds: async () => [] } as any,
    // Snapshot path only; the router (over a no-op source) is never invoked here.
    router: new EnvChangeRouter({
      source: { subscribeToEnv: () => () => {} },
      hydrator: { hydrateByIds: async () => (row ? [row] : []) },
      replayWindowMs: 0,
      unsubscribeLingerMs: 0,
    }),
    limiter: { incrementAndCheck: async () => true, decrement: async () => {} } as any,
    cachedLimitProvider: { getCachedLimit: async () => 100 },
    maximumCreatedAtFilterAgeMs: 24 * 60 * 60 * 1000,
  });
}

const ENV: RealtimeListEnvironment = {
  id: "env_1",
  organizationId: "org_1",
  projectId: "proj_1",
};

describe("NativeRealtimeClient response headers", () => {
  it("exposes electric headers cross-origin so browser hooks can read them", async () => {
    const client = makeClient(sampleRow());
    const res = await client.streamRun(
      "http://localhost:3030/realtime/v1/runs/run_1?offset=-1",
      ENV,
      "run_1",
      CURRENT_API_VERSION,
      undefined,
      "1.0.0-beta.1" // modern client => lowercase electric-* headers
    );

    // Without these the deployed @electric-sql/client throws MissingHeadersError
    // (it can't read the electric-* headers across origins). This regressed once.
    expect(res.headers.get("access-control-allow-origin")).toBe("*");
    expect(res.headers.get("access-control-expose-headers")).toBe("*");

    // Initial (non-live) snapshot requires offset + handle + schema.
    expect(res.headers.get("electric-offset")).toBeTruthy();
    expect(res.headers.get("electric-handle")).toBeTruthy();
    expect(res.headers.get("electric-schema")).toBeTruthy();
    expect(res.headers.get("content-type")).toBe("application/json");
  });

  it("renames headers for legacy (0.4.0) clients", async () => {
    const client = makeClient(sampleRow());
    const res = await client.streamRun(
      "http://localhost:3030/realtime/v1/runs/run_1?offset=-1",
      ENV,
      "run_1",
      CURRENT_API_VERSION,
      undefined,
      undefined // no client version => legacy header names
    );

    expect(res.headers.get("electric-chunk-last-offset")).toBeTruthy();
    expect(res.headers.get("electric-shape-id")).toBeTruthy();
    expect(res.headers.get("electric-offset")).toBeNull();
    expect(res.headers.get("electric-handle")).toBeNull();
    expect(res.headers.get("access-control-expose-headers")).toBe("*");
  });
});
