import { Miniflare } from 'miniflare'
import type { D1Database } from '@cloudflare/workers-types'
import { describe, expect, it } from 'vitest'
import { runRecordStoreConformance } from '../conformance.js'
import type { RecordStore } from '../types.js'
import { makeD1RecordStore, recordStoreD1TableSql } from './index.js'

const TABLE = 'record_store'
const D1_BINDING = 'DB'
const D1_DATABASE_ID = '00000000-0000-4000-8000-000000000001'

type JsonRecord = Record<string, unknown>

let sharedD1: D1Database | undefined
let miniflare: Miniflare | undefined

async function getTestD1(): Promise<D1Database> {
  if (sharedD1 === undefined) {
    miniflare = new Miniflare({
      modules: true,
      script: `
        export default {
          async fetch() {
            return new Response("ok");
          },
        }
      `,
      d1Databases: { [D1_BINDING]: D1_DATABASE_ID },
    })
    sharedD1 = await miniflare.getD1Database(D1_BINDING)
    await sharedD1.exec(recordStoreD1TableSql(TABLE))
  }
  return sharedD1
}

async function resetTable(d1: D1Database): Promise<void> {
  await d1.prepare(`DELETE FROM ${TABLE}`).run()
  await d1.prepare(`DELETE FROM sqlite_sequence WHERE name = ?`).bind(TABLE).run()
}

async function setupStore(opts?: {
  now?: () => string
  makeId?: () => string
}): Promise<RecordStore<JsonRecord>> {
  const d1 = await getTestD1()
  await resetTable(d1)
  return makeD1RecordStore<JsonRecord>(d1, TABLE, opts)
}

describe('makeD1RecordStore', () => {
  it('conforms', async () => {
    await runRecordStoreConformance((opts) => setupStore(opts))
  }, 30_000)
})

describe('recordStoreD1TableSql', () => {
  it('creates both the table and the keyset index when applied', async () => {
    const mf = new Miniflare({
      modules: true,
      script: `
        export default {
          async fetch() {
            return new Response("ok");
          },
        }
      `,
      d1Databases: { [D1_BINDING]: '00000000-0000-4000-8000-000000000002' },
    })
    try {
      const d1 = await mf.getD1Database(D1_BINDING)
      const ddlTable = 'ddl_record_store'
      const ddl = recordStoreD1TableSql(ddlTable)

      // CF D1's `.exec()` splits a multi-statement string on NEWLINES; a
      // space-joined single line would silently skip CREATE INDEX on real D1.
      // Pin the newline-separated form: exactly 2 non-empty statements, one
      // CREATE TABLE and one CREATE INDEX, each a complete (newline-free) string.
      const statements = ddl.split('\n').filter((s) => s.trim().length > 0)
      expect(statements).toHaveLength(2)
      expect(statements.filter((s) => /create table/i.test(s))).toHaveLength(1)
      expect(statements.filter((s) => /create index/i.test(s))).toHaveLength(1)

      await d1.exec(ddl)

      const result = await d1
        .prepare(
          `SELECT name, type FROM sqlite_master
           WHERE type IN ('table', 'index') AND name LIKE ?
           ORDER BY name`,
        )
        .bind(`${ddlTable}%`)
        .all<{ name: string; type: string }>()
      const objects = result.results ?? []

      // The keyset contract's perf half (spec §3/§4) — the conformance suite
      // cannot catch a missing index (queries stay correct, just slower), so
      // assert the index exists alongside the table.
      expect(objects.some((o) => o.type === 'table' && o.name === ddlTable)).toBe(true)
      expect(
        objects.some(
          (o) => o.type === 'index' && o.name === `${ddlTable}_created_at_ms_seq_idx`,
        ),
      ).toBe(true)
    } finally {
      await mf.dispose()
    }
  }, 30_000)
})
