import { Miniflare } from 'miniflare'
import { sql } from 'drizzle-orm'
import { afterEach, describe, expect, it } from 'vitest'
import { createD1Client, isD1BatchError } from '@platform-modules/db/sqlite/d1'
import { bookType } from './lifecycle.test-helpers.js'
import { defineContentType, resolveContentType } from './registry.js'
import { defineContentStatus, resolveContentStatus } from './status.js'
import { prepareContentRevisionD1 } from './lifecycle.js'
import type { ContentEntry } from './model.js'
import type { ContentPrincipal } from './authz.js'

const workers: Miniflare[] = []
afterEach(async () => { await Promise.all(workers.splice(0).map((worker) => worker.dispose())) })

async function fixture() {
  const worker = new Miniflare({ modules: true, script: 'export default { fetch(){ return new Response("ok") } }', d1Databases: ['DB'] })
  workers.push(worker)
  const binding = await worker.getD1Database('DB')
  await binding.exec(`
    CREATE TABLE content_type_definitions (key TEXT PRIMARY KEY, origin TEXT NOT NULL, version INTEGER NOT NULL, active INTEGER NOT NULL, current_revision INTEGER NOT NULL, canonical_hash TEXT NOT NULL, definition TEXT NOT NULL, shadowed_db_version INTEGER);
    CREATE TABLE content_status_definitions (key TEXT PRIMARY KEY, origin TEXT NOT NULL, version INTEGER NOT NULL, active INTEGER NOT NULL, current_revision INTEGER NOT NULL, canonical_hash TEXT NOT NULL, definition TEXT NOT NULL, shadowed_db_version INTEGER);
    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, parent_id TEXT, menu_order INTEGER NOT NULL, template_key TEXT, excerpt TEXT NOT NULL, featured_media TEXT, comment_status TEXT NOT NULL, ping_status TEXT NOT NULL, sticky INTEGER NOT NULL, format TEXT, deleted_at TEXT, last_edited_by TEXT NOT NULL, type_definition_revision INTEGER NOT NULL, status_definition_revision INTEGER NOT NULL);
    CREATE TABLE content_password_credentials (entry_id TEXT PRIMARY KEY, credential_version TEXT NOT NULL, credential TEXT NOT NULL);
    CREATE TABLE content_terms (id TEXT PRIMARY KEY, taxonomy TEXT NOT NULL, slug TEXT NOT NULL, name TEXT NOT NULL, parent_id TEXT, depth INTEGER NOT NULL);
    CREATE TABLE content_entry_terms (entry_id TEXT NOT NULL, term_id TEXT NOT NULL, PRIMARY KEY(entry_id,term_id));
    CREATE TABLE content_revisions (seq INTEGER PRIMARY KEY AUTOINCREMENT, id TEXT NOT NULL UNIQUE, entry_id TEXT NOT NULL, title TEXT NOT NULL, body TEXT NOT NULL, slug TEXT NOT NULL, type TEXT NOT NULL, term_ids TEXT NOT NULL, snapshot TEXT, editor TEXT NOT NULL, created_at TEXT NOT NULL);
  `)
  const client = createD1Client(binding as never)
  const typeDefinition = defineContentType(bookType())
  const statusDefinition = defineContentStatus({ key: 'draft', label: 'Draft', published: false, internal: false, excludeFromSearch: true, publiclyQueryable: false, showInAdminAll: true, showInAdminStatusFilter: true, dateLabel: 'lastModified', transitionInput: 'none' })
  const now = '2026-08-23T06:00:00.000Z'
  await binding.prepare('INSERT INTO content_type_definitions (key,origin,version,active,current_revision,canonical_hash,definition) VALUES (?,?,?,?,?,?,?)').bind('book','code',1,1,1,'a'.repeat(64),JSON.stringify(typeDefinition)).run()
  await binding.prepare('INSERT INTO content_status_definitions (key,origin,version,active,current_revision,canonical_hash,definition) VALUES (?,?,?,?,?,?,?)').bind('draft','code',1,1,1,'b'.repeat(64),JSON.stringify(statusDefinition)).run()
  await binding.prepare('INSERT INTO content_entries VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)').bind('entry-1','book','book','Title','Body','draft','public',null,'author-1',now,now,null,0,null,'',null,'open','open',0,null,null,'author-1',1,1).run()
  await binding.prepare('INSERT INTO content_terms VALUES (?,?,?,?,?,?)').bind('term-1','topic','one','One',null,0).run()
  await binding.prepare('INSERT INTO content_entry_terms VALUES (?,?)').bind('entry-1','term-1').run()
  const [type, status] = await Promise.all([resolveContentType(client, 'book'), resolveContentStatus(client, 'draft')])
  const entry: ContentEntry = Object.freeze({ id:'entry-1', slug:'book', type:'book', title:'Title', body:'Body', status:'draft', visibility:'public', publishedAt:null, author:'author-1', terms:[Object.freeze({id:'term-1',taxonomy:'topic',slug:'one',name:'One',parentId:null,depth:0})], createdAt:new Date(now), updatedAt:new Date(now), parentId:null, menuOrder:0, templateKey:null, excerpt:'', featuredMedia:null, commentStatus:'open', pingStatus:'open', passwordProtected:false, sticky:false, format:null, deletedAt:null, lastEditedBy:'author-1', typeDefinitionRevision:1, statusDefinitionRevision:1 })
  const principal: ContentPrincipal = Object.freeze({ id:'author-1', capabilities:new Set<string>(['edit_own']) })
  return { client, entry, type, status, principal }
}

describe('prepareContentRevisionD1', () => {
  it('authorizes caller-held evidence before any content lookup', async () => {
    const { client, entry, type, status } = await fixture()
    let reads = 0
    const denied: ContentPrincipal = Object.freeze({ id:'author-1', capabilities:new Set<string>() })
    await expect(prepareContentRevisionD1({ prepare: client.prepare, execute: async (query) => { reads += 1; return client.execute(query) } }, { entry, type, status }, denied)).rejects.toMatchObject({ name:'ContentAuthorizationError' })
    expect(reads).toBe(0)
  })

  it('aborts the host batch when term assignments drift after preparation', async () => {
    const { client, entry, type, status, principal } = await fixture()
    const contribution = await prepareContentRevisionD1(client, { entry, type, status }, principal)
    await client.execute(sql`DELETE FROM content_entry_terms WHERE entry_id=${entry.id} AND term_id='term-1'`)
    await client.execute(sql`INSERT INTO content_terms (id,taxonomy,slug,name,parent_id,depth) VALUES ('term-2','topic','two','Two',NULL,0)`)
    await client.execute(sql`INSERT INTO content_entry_terms (entry_id,term_id) VALUES (${entry.id},'term-2')`)
    await expect(client.batch(contribution.items)).rejects.toSatisfy(isD1BatchError)
    expect(await client.execute(sql`SELECT id FROM content_revisions`)).toEqual([])
  })
})
