import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { test } from "bun:test";
import { fileURLToPath } from "node:url";

import { TABLES, assertProtocolRecord, assertProtocolRecords } from "../protocol/index.mjs";
import {
  CLOUD_CORE_MIGRATIONS,
  CLOUD_CORE_MIGRATION_ID,
  CLOUD_RUNTIME_DURABLE_ADAPTER_SCHEMA_VERSION,
  CLOUD_RUNTIME_DURABLE_MIGRATION_TABLE,
  CLOUD_RUNTIME_DURABLE_MIGRATION_TABLE_COLUMNS,
  CLOUD_RUNTIME_DURABLE_TABLE_COLUMNS,
  FROZEN_CLOUD_CORE_TABLES,
  assertFrozenCloudCoreTables,
  requiredRuntimeDurableColumns
} from "./schema.ts";

const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");

test("cloud core schema keeps the frozen L0 table names", () => {
  assert.deepEqual(FROZEN_CLOUD_CORE_TABLES, TABLES);
  assertFrozenCloudCoreTables();
  assert.equal(CLOUD_CORE_MIGRATIONS[0].connectsToDatabase, false);
});

test("initial migration skeleton declares every frozen table", async () => {
  const sql = await readFile(path.join(repoRoot, "internal/db/migrations/0001_cloud_core_skeleton.sql"), "utf8");

  for (const table of TABLES) {
    assert.match(sql, new RegExp(`CREATE TABLE IF NOT EXISTS ${table}\\b`), `missing ${table}`);
  }

  assert.match(sql, /\brequest_id\b/);
  assert.match(sql, /\bactor\b/);
  assert.match(sql, /\bsource\b/);
  assert.match(sql, /\boperation\b/);
  assert.match(sql, /\btarget\b/);
  assert.match(sql, /\bresult\b/);
  assert.match(sql, /\btimestamp\b/);
});

test("initial migration exposes columns required by the durable runtime adapter", async () => {
  const sql = await readFile(path.join(repoRoot, "internal/db/migrations/0001_cloud_core_skeleton.sql"), "utf8");

  assert.ok(requiredRuntimeDurableColumns().length > 0);
  for (const [table, columns] of Object.entries(CLOUD_RUNTIME_DURABLE_TABLE_COLUMNS)) {
    const tableMatch = sql.match(new RegExp(`CREATE TABLE IF NOT EXISTS ${table}\\s*\\(([\\s\\S]*?)\\);`, "u"));
    assert.ok(tableMatch, `missing ${table}`);
    for (const column of columns) {
      assert.match(tableMatch[1], new RegExp(`\\b${column}\\b`), `missing ${table}.${column}`);
    }
  }

  const migrationMatch = sql.match(
    new RegExp(`CREATE TABLE IF NOT EXISTS ${CLOUD_RUNTIME_DURABLE_MIGRATION_TABLE}\\s*\\(([\\s\\S]*?)\\);`, "u")
  );
  assert.ok(migrationMatch, `missing ${CLOUD_RUNTIME_DURABLE_MIGRATION_TABLE}`);
  for (const column of CLOUD_RUNTIME_DURABLE_MIGRATION_TABLE_COLUMNS) {
    assert.match(migrationMatch[1], new RegExp(`\\b${column}\\b`), `missing migration ledger column ${column}`);
  }
  assert.match(sql, new RegExp(`'${CLOUD_CORE_MIGRATION_ID}'`, "u"));
  assert.match(sql, new RegExp(`'${CLOUD_RUNTIME_DURABLE_ADAPTER_SCHEMA_VERSION}'`, "u"));
  assert.equal(CLOUD_CORE_MIGRATIONS[0].runtimeDurableMigrationTable, CLOUD_RUNTIME_DURABLE_MIGRATION_TABLE);
});

test("initial migration declares Workbench fact backfill sources", async () => {
  const sql = await readFile(path.join(repoRoot, "internal/db/migrations/0001_cloud_core_skeleton.sql"), "utf8");

  for (const [target, source] of [
    ["workbench_sessions", "agent_sessions"],
    ["workbench_trace_events", "agent_trace_events"],
    ["workbench_turns", "workbench_projection_state"],
    ["workbench_projection_checkpoints", "workbench_projection_state"]
  ]) {
    assert.match(sql, new RegExp(`INSERT INTO ${target} \\(`, "u"), `missing ${target} backfill`);
    assert.match(sql, new RegExp(`FROM ${source}\\b`, "u"), `missing ${source} source for ${target}`);
  }

  assert.match(sql, /ON CONFLICT \(session_id\) DO NOTHING/u);
  assert.match(sql, /ON CONFLICT \(trace_id\) DO NOTHING/u);
  const traceBackfillMatch = sql.match(/INSERT INTO workbench_trace_events \([\s\S]*?ON CONFLICT DO NOTHING;/u);
  assert.ok(traceBackfillMatch, "missing workbench_trace_events idempotent backfill");
  assert.match(traceBackfillMatch[0], /ROW_NUMBER\(\) OVER \(PARTITION BY trace_id ORDER BY occurred_at, id\)/u);
  assert.match(traceBackfillMatch[0], /durable_projection_seq/u);
  assert.match(sql, /duplicate_trace_source_events/u);
  assert.match(sql, /workbench_trace_events:/u);
  assert.match(sql, /trace_event_projected_seq_conflicts/u);
  assert.match(sql, /COUNT\(\*\) <> COUNT\(DISTINCT projected_seq\)/u);
  assert.match(sql, /DROP INDEX IF EXISTS idx_workbench_trace_events_trace_projected_seq/u);
  assert.match(sql, /CREATE UNIQUE INDEX idx_workbench_trace_events_trace_projected_seq/u);
});

test("protocol record guards catch schema drift before runtime writes", () => {
  assertProtocolRecord("gatewaySession", {
    gatewaySessionId: "gws_01J00000000000000000000000",
    projectId: "prj_01J00000000000000000000000",
    serviceId: "hwlab-gateway",
    gatewayId: "gtw_01J00000000000000000000000",
    endpoint: "http://127.0.0.1:7101",
    status: "connected",
    environment: "dev",
    startedAt: "2026-05-21T00:00:00.000Z",
    lastSeenAt: "2026-05-21T00:00:00.000Z"
  });

  assertProtocolRecords("boxCapability", [
    {
      capabilityId: "cap_01J00000000000000000000000",
      resourceId: "res_01J00000000000000000000000",
      projectId: "prj_01J00000000000000000000000",
      name: "shell.exec",
      direction: "bidirectional",
      valueType: "object",
      mutatesState: true,
      createdAt: "2026-05-21T00:00:00.000Z",
      updatedAt: "2026-05-21T00:00:00.000Z"
    }
  ]);

  assert.throws(
    () =>
      assertProtocolRecord("evidenceRecord", {
        evidenceId: "evd_01J00000000000000000000000",
        projectId: "prj_01J00000000000000000000000",
        operationId: "op_01J00000000000000000000000",
        kind: "trace",
        uri: "memory://hwlab/evidence/evd_01J00000000000000000000000",
        sha256: "0".repeat(64),
        serviceId: "hwlab-cloud-api",
        environment: "dev",
        createdAt: "2026-05-21T00:00:00.000Z",
        evidenceType: "trace"
      }),
    /evidenceRecord.evidenceType is not part of the frozen protocol schema/
  );
});
