import { afterEach, describe, expect, it } from 'vitest'
import { sql } from 'drizzle-orm'
import { lifecycleFixture, admin, reader, bookType } from './lifecycle.test-helpers.js'
import { canonicalContentMigrationHash } from './migrations/content-model.js'
import { normalizeContentTypeDefinition } from './registry.js'
import { exportContent, type ContentExportAudit } from './export.js'
import { parseContentImport, type ContentImportLimits } from './import.js'
import type { ContentPrincipal } from './authz.js'

const fixed='2026-08-22T00:00:00.000Z'
const entryId='33333333-3333-4333-8333-333333333333'
const revisionId='44444444-4444-4444-8444-444444444444'
const limits:ContentImportLimits=Object.freeze({maxBytes:2_000_000,maxDefinitions:100,maxEntries:100,maxRevisions:100,batchSize:10})
let stop:(()=>Promise<void>)|undefined
afterEach(async()=>{await stop?.();stop=undefined})

async function fixture(){const value=await lifecycleFixture();stop=value.stop;return value.db}
async function insertProtectedEntry(db:Awaited<ReturnType<typeof fixture>>){
  await db.execute(sql`INSERT INTO content_entries (id,slug,type,title,body,status,visibility,published_at,author,created_at,updated_at,parent_id,menu_order,template_key,excerpt,featured_media,comment_status,ping_status,sticky,format,deleted_at,last_edited_by,type_definition_revision,status_definition_revision)
    VALUES (${entryId},'exported-book','book','Exported Book','Body','draft','public',NULL,'admin',${fixed},${fixed},NULL,0,'default','Excerpt',NULL,'open','closed',false,NULL,NULL,'admin',1,1)`)
  await db.execute(sql`INSERT INTO content_password_credentials (entry_id,credential_version,credential,created_at,updated_at) VALUES (${entryId},'cred-v1','HASH-SECRET-MUST-NOT-EXPORT',${fixed},${fixed})`)
  await db.execute(sql`INSERT INTO content_revisions (id,entry_id,title,body,slug,type,term_ids,snapshot,editor,created_at) VALUES (${revisionId},${entryId},'Exported Book','Body','exported-book','book',${JSON.stringify([])},${JSON.stringify({title:'Exported Book',body:'Body',visibility:'public'})},'admin',${fixed})`)
}
async function advanceType(db:Awaited<ReturnType<typeof fixture>>,canExport:boolean){
  const definition=normalizeContentTypeDefinition({...bookType(),canExport,description:canExport?'Revision two':'Revision two export disabled'}),hash=await canonicalContentMigrationHash(definition)
  await db.execute(sql`UPDATE content_type_definitions SET version=2,current_revision=2,canonical_hash=${hash},definition=${JSON.stringify(definition)},updated_at=${fixed} WHERE key='book'`)
  await db.execute(sql`INSERT INTO content_definition_versions (definition_kind,definition_key,revision,canonical_hash,definition,origin,created_at) VALUES ('type','book',2,${hash},${JSON.stringify(definition)},'db',${fixed})`)
}
const audit:ContentExportAudit=Object.freeze({recordOverride:async()=>{}})

describe('native content export',()=>{
  it('exports historical definition pins and revisions without password credentials, and round-trips through the parser',async()=>{
    const db=await fixture();await insertProtectedEntry(db);await advanceType(db,true)
    const bundle=await db.transaction((tx)=>exportContent(tx,admin,{typeKeys:['book'],includeRevisions:true,limits},{audit}))
    expect(bundle.types).toHaveLength(1);expect(bundle.statuses.length).toBeGreaterThan(0);expect(bundle.entries).toHaveLength(1);expect(bundle.revisions).toHaveLength(1)
    const type=bundle.types[0] as Record<string,unknown>,versions=type.versions as readonly Record<string,unknown>[],entry=bundle.entries[0] as Record<string,unknown>
    expect(type).toMatchObject({key:'book',revision:2,version:2});expect(versions.map((item)=>item.revision)).toEqual([1,2]);expect(entry).toMatchObject({id:entryId,typeDefinitionRevision:1,statusDefinitionRevision:1});expect(entry).not.toHaveProperty('passwordProtected')
    const serialized=JSON.stringify(bundle);expect(serialized).not.toContain('HASH-SECRET-MUST-NOT-EXPORT');expect(serialized.toLowerCase()).not.toContain('credential')
    const parsed=await parseContentImport(new TextEncoder().encode(serialized),limits);expect(parsed).toEqual(bundle)
  })

  it('preflights read authority before entry lookup and requires audited manageType override for canExport=false',async()=>{
    const db=await fixture();await advanceType(db,false)
    const none:ContentPrincipal=Object.freeze({id:'none',capabilities:new Set<string>()})
    await db.execute(sql`ALTER TABLE content_entries RENAME TO content_entries_hidden`)
    await expect(db.transaction((tx)=>exportContent(tx,none,{typeKeys:['book'],includeRevisions:false,limits},{audit}))).rejects.toMatchObject({name:'ContentAuthorizationError'})
  })

  it('fails closed on canExport=false override denial/audit failure and records a successful override',async()=>{
    const db=await fixture();await insertProtectedEntry(db);await advanceType(db,false)
    await expect(db.transaction((tx)=>exportContent(tx,admin,{typeKeys:['book'],includeRevisions:false,limits},{audit}))).rejects.toMatchObject({code:'forbidden'})
    await expect(db.transaction((tx)=>exportContent(tx,reader,{typeKeys:['book'],includeRevisions:false,limits,overrideCanExport:{reason:'migration',operationId:'export:denied'}},{audit}))).rejects.toMatchObject({name:'ContentAuthorizationError'})
    await expect(db.transaction((tx)=>exportContent(tx,admin,{typeKeys:['book'],includeRevisions:false,limits,overrideCanExport:{reason:'migration',operationId:'export:audit-fail'}},{audit:{recordOverride:async()=>{throw new Error('audit failed')}}}))).rejects.toThrow('audit failed')
    const calls:{typeKeys:readonly string[];reason:string;operationId:string}[]=[]
    const bundle=await db.transaction((tx)=>exportContent(tx,admin,{typeKeys:['book'],includeRevisions:false,limits,overrideCanExport:{reason:'migration',operationId:'export:ok'}},{audit:{recordOverride:async(_tx,_principal,typeKeys,override)=>{calls.push({typeKeys,reason:override.reason,operationId:override.operationId})}}}))
    expect(calls).toEqual([{typeKeys:['book'],reason:'migration',operationId:'export:ok'}]);expect(bundle.entries).toHaveLength(1)
  })
})
