import { sql } from "drizzle-orm";
import { Miniflare } from "miniflare";
import { describe, expect, it } from "vitest";
import { getTransactionIdentity } from "@platform-modules/db";
import { createD1Client, isD1BatchError } from "@platform-modules/db/sqlite/d1";
import { startPg } from "./pg-harness.js";
import { contentSchema, type ContentSchema } from "./schema.js";
import {
  assertContentTransactionIdentity,
  isContentStoreContractError,
  readFlatContentEntries,
} from "./store.js";

type FlatRow = Record<string, unknown>;

const corpus = [
  {
    id: "entry-1",
    slug: "legacy-entry",
    type: "unregistered_legacy_type",
    title: "Legacy entry",
    body: "<p>preserved</p>",
    status: "legacy-status",
    visibility: "members",
    publishedAt: "2026-08-08T12:00:00.000Z",
    author: "author-1",
    createdAt: "2026-08-01T12:00:00.000Z",
    updatedAt: "2026-08-07T12:00:00.000Z",
  },
] satisfies readonly FlatRow[];

async function d1Fixture(rows: readonly FlatRow[]) {
  const miniflare = new Miniflare({
    modules: true,
    script: `export default { fetch() { return new Response("ok") } }`,
    d1Databases: { DB: "00000000-0000-4000-8000-000000000017" },
  });
  const binding = await miniflare.getD1Database("DB");
  await binding.exec(
    `CREATE TABLE content_entries (id TEXT PRIMARY KEY, slug TEXT NOT NULL, type TEXT NOT NULL, title TEXT NOT NULL, body TEXT NOT NULL, status TEXT NOT NULL, visibility TEXT NOT NULL, published_at TEXT, author TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL)`
  );
  for (const row of rows) {
    await binding.prepare(`INSERT INTO content_entries
      (id, slug, type, title, body, status, visibility, published_at, author, created_at, updated_at)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
      .bind(row.id, row.slug, row.type, row.title, row.body, row.status, row.visibility, row.publishedAt, row.author, row.createdAt, row.updatedAt).run();
  }
  const client = createD1Client<ContentSchema>(binding, { schema: contentSchema });
  return {
    name: "D1 fixture" as const,
    db: client,
    async dispose() { await miniflare.dispose(); },
  };
}

function requireFixture<T>(fixture: T | undefined, name: string): T {
  if (fixture === undefined)
    throw new Error(`${name} is required for adapter conformance`);
  return fixture;
}

async function readPostgres(db: { transaction<T>(callback: (tx: any) => Promise<T>): Promise<T> }) {
  return db.transaction(async (tx) => {
    const identity = getTransactionIdentity(tx);
    assertContentTransactionIdentity(tx, identity);
    return readFlatContentEntries({ adapter: "postgres", execute: tx.execute });
  });
}

describe("content final-store adapter conformance", () => {
  it("executes the identified D1 fixture and embedded real-Postgres fixture with equivalent flat values", async () => {
    let d1: Awaited<ReturnType<typeof d1Fixture>> | undefined;
    let pg: Awaited<ReturnType<typeof startPg>> | undefined;
    try {
      d1 = requireFixture(await d1Fixture(corpus), "D1 fixture");
      expect(d1.name).toBe("D1 fixture");
      pg = requireFixture(await startPg(), "embedded real-Postgres fixture");
      await pg.db.execute(sql`
        CREATE TABLE content_entries (
          id text PRIMARY KEY,
          slug text NOT NULL,
          type text NOT NULL,
          title text NOT NULL,
          body text NOT NULL,
          status text NOT NULL,
          visibility text NOT NULL,
          published_at timestamptz,
          author text NOT NULL,
          created_at timestamptz NOT NULL,
          updated_at timestamptz NOT NULL
        )
      `);
      await pg.db.execute(sql`
        INSERT INTO content_entries (
          id, slug, type, title, body, status, visibility, published_at, author, created_at, updated_at
        ) VALUES (
          'entry-1', 'legacy-entry', 'unregistered_legacy_type', 'Legacy entry', '<p>preserved</p>',
          'legacy-status', 'members', '2026-08-08T12:00:00.000Z', 'author-1',
          '2026-08-01T12:00:00.000Z', '2026-08-07T12:00:00.000Z'
        )
      `);

      await d1.db.execute(sql`UPDATE content_entries SET title = 'Mutated through D1 SQL' WHERE id = 'entry-1'`);
      const d1Values = await readFlatContentEntries({ adapter: "d1", execute: d1.db.execute });
      const pgDb = pg.db;
      const pgValues = await readPostgres(pgDb);

      expect(d1Values[0]?.title).toBe("Mutated through D1 SQL");
      expect(pgValues[0]?.title).toBe("Legacy entry");
      await pg.db.execute(sql`UPDATE content_entries SET title = 'Mutated through D1 SQL' WHERE id = 'entry-1'`);
      expect(d1Values).toEqual(await readPostgres(pgDb));
      expect(d1Values[0]?.type).toBe("unregistered_legacy_type");
      expect(d1Values[0]?.status).toBe("legacy-status");
      expect(Object.isFrozen(d1Values)).toBe(true);
      expect(Object.isFrozen(d1Values[0]!)).toBe(true);

      await expect(d1.db.batch([
        d1.db.prepare(sql`UPDATE content_entries SET title = 'rolled back' WHERE id = 'entry-1'`),
        d1.db.prepare(sql`INSERT INTO missing_d1_table VALUES ('fails')`),
      ])).rejects.toSatisfy(isD1BatchError);
      const afterD1Rollback = await readFlatContentEntries({ adapter: "d1", execute: d1.db.execute });
      expect(afterD1Rollback.find((value) => value.id === 'entry-1')?.title).not.toBe("rolled back");

      let firstPgIdentity:
        | ReturnType<typeof getTransactionIdentity>
        | undefined;
      await pgDb.transaction(async (tx) => {
        firstPgIdentity = getTransactionIdentity(tx);
      });
      await pgDb.transaction(async (tx) => {
        expect(() =>
          assertContentTransactionIdentity(tx, firstPgIdentity!)
        ).toThrowError(
          expect.objectContaining({
            code: "transaction-capability",
            detail: "identity-mismatch",
          })
        );
      });
      await expect(pgDb.transaction(async (tx) => {
        await tx.execute(sql`UPDATE content_entries SET title = 'postgres rolled back' WHERE id = 'entry-1'`);
        throw new Error("force PostgreSQL rollback");
      })).rejects.toThrow("force PostgreSQL rollback");
      const afterPgRollback = await readPostgres(pgDb);
      expect(afterPgRollback.find((value) => value.id === "entry-1")?.title).toBe("Mutated through D1 SQL");
    } finally {
      await pg?.stop();
      await d1?.dispose();
    }
  }, 90_000);

  it("returns a structural error for malformed adapter rows rather than interpreting them", async () => {
    try {
      await readFlatContentEntries({
        adapter: "d1",
        execute: async <Row extends Record<string, unknown>>() =>
          [{ ...corpus[0], id: 42 }] as unknown as readonly Row[],
      });
      throw new Error("expected malformed row to reject");
    } catch (error) {
      expect(isContentStoreContractError(error)).toBe(true);
      expect(error).toMatchObject({ code: "invalid-flat-row", field: "id" });
    }
  });

  it("rejects accessor-backed rows without invoking the accessor", async () => {
    const row = { ...corpus[0] };
    Object.defineProperty(row, "id", {
      enumerable: true,
      get: () => {
        throw new Error("row accessor executed");
      },
    });

    await expect(readFlatContentEntries({
      adapter: "d1",
      execute: async <Row extends Record<string, unknown>>() => [row] as unknown as readonly Row[],
    })).rejects.toMatchObject({
      code: "invalid-flat-row",
      field: "row",
    });
  });
});
