import { describe,expect,it } from 'vitest';
import { sql } from 'drizzle-orm';
import { createPgliteClient } from '@platform-modules/db/pglite';
import {
  canonicalContentMigrationHash,
  contentModelForwardMigrationSql,
  contentRevisionsMigrationSql,
  contentTaxonomyMigrationSql,
  normalizeContentStatusDefinition,
  normalizeContentTypeDefinition,
  type ContentTypeCapabilities,
  type ContentTypeDefinition,
  type ContentTypeLabels,
} from '@platform-modules/content';
import { fullSchema } from './db.js';
import { issueContentProof,resolveContentModelPublicRouteFromDb,resolveContentModelRouteTemplate,verifyContentProof } from './content-model-routes.js';

const fixed='2026-08-23T00:00:00.000Z',parentId='11111111-1111-4111-8111-111111111111',childId='22222222-2222-4222-8222-222222222222',secret='route-proof-secret';
const LEGACY=`CREATE TABLE content_entries (id uuid 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(3),author text NOT NULL,created_at timestamptz(3) NOT NULL,updated_at timestamptz(3) NOT NULL)`;
function caps():ContentTypeCapabilities{return {manageType:'book.manage',migrateAll:'book.migrate',manageTerms:'book.terms',create:'book.create',read:'book.read',readPrivate:'book.readPrivate',readProtected:'book.readProtected',editOwn:'book.editOwn',editOthers:'book.editOthers',editPrivate:'book.editPrivate',editPublished:'book.editPublished',publish:'book.publish',deleteOwn:'book.deleteOwn',deleteOthers:'book.deleteOthers',deletePrivate:'book.deletePrivate',deletePublished:'book.deletePublished'};}
function labels():ContentTypeLabels{return {name:'Books',singularName:'Book',menuName:'Books',nameAdminBar:'Book',addNew:'Add New',addNewItem:'Add New Book',editItem:'Edit Book',newItem:'New Book',viewItem:'View Book',viewItems:'View Books',searchItems:'Search Books',notFound:'Not found',notFoundInTrash:'Not found in trash',parentItemColon:'Parent:',allItems:'All Books',archives:'Archives',attributes:'Attributes',insertIntoItem:'Insert',uploadedToThisItem:'Uploaded',featuredImage:'Featured',setFeaturedImage:'Set featured',removeFeaturedImage:'Remove featured',useFeaturedImage:'Use featured',filterItemsList:'Filter',filterByDate:'Filter date',itemsListNavigation:'Navigation',itemsList:'List',itemPublished:'Published',itemPublishedPrivately:'Published privately',itemRevertedToDraft:'Draft',itemScheduled:'Scheduled',itemUpdated:'Updated',itemLink:'Link',itemLinkDescription:'Link description'};}
function typeDef():ContentTypeDefinition{return {key:'book',labels:labels(),public:true,hierarchical:true,excludeFromSearch:false,publiclyQueryable:true,showUi:true,showInMenu:true,showInNavMenus:true,showInAdminBar:true,capabilities:caps(),supports:['title','editor','revisions'],taxonomies:[],hasArchive:true,rewrite:{slug:'books',pages:true,feeds:true},queryVariable:'book',canExport:true,deleteWithAuthor:false,rest:{base:'books',namespace:'content/v1',revisions:true,autosaves:true},defaultTemplateKey:'default',statusKeys:['draft','live'],active:true};}
async function fixture(){const db=createPgliteClient({schema:fullSchema});await db.execute(sql.raw(LEGACY));for(const statement of contentRevisionsMigrationSql().split(';').map((v)=>v.trim()).filter(Boolean))await db.execute(sql.raw(statement));for(const statement of contentModelForwardMigrationSql('postgres'))await db.execute(sql.raw(statement));for(const statement of contentTaxonomyMigrationSql().split(';').map((v)=>v.trim()).filter(Boolean))await db.execute(sql.raw(statement));const type=normalizeContentTypeDefinition(typeDef()),draft=normalizeContentStatusDefinition({key:'draft',label:'Draft',published:false,internal:false,excludeFromSearch:true,publiclyQueryable:false,showInAdminAll:true,showInAdminStatusFilter:true,dateLabel:'lastModified',transitionInput:'none'}),live=normalizeContentStatusDefinition({key:'live',label:'Live',published:true,internal:false,excludeFromSearch:false,publiclyQueryable:true,showInAdminAll:true,showInAdminStatusFilter:true,dateLabel:'published',transitionInput:'publishedAt'}),typeHash=await canonicalContentMigrationHash(type),draftHash=await canonicalContentMigrationHash(draft),liveHash=await canonicalContentMigrationHash(live);await db.execute(sql`INSERT INTO content_type_definitions (key,origin,version,active,current_revision,canonical_hash,definition,created_at,updated_at) VALUES ('book','db',1,true,1,${typeHash},${JSON.stringify(type)},${fixed},${fixed})`);await db.execute(sql`INSERT INTO content_status_definitions (key,origin,version,active,current_revision,canonical_hash,definition,created_at,updated_at) VALUES ('draft','db',1,true,1,${draftHash},${JSON.stringify(draft)},${fixed},${fixed}),('live','db',1,true,1,${liveHash},${JSON.stringify(live)},${fixed},${fixed})`);await db.execute(sql`INSERT INTO content_definition_versions (definition_kind,definition_key,revision,canonical_hash,definition,origin,created_at) VALUES ('type','book',1,${typeHash},${JSON.stringify(type)},'db',${fixed}),('status','draft',1,${draftHash},${JSON.stringify(draft)},'db',${fixed}),('status','live',1,${liveHash},${JSON.stringify(live)},'db',${fixed})`);for(const [id,slug,parent,title,body] of [[parentId,'parent',null,'Parent','Parent body'],[childId,'child',parentId,'Child','Secret body']] as const)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 (${id},${slug},'book',${title},${body},'live','public',${fixed},'author',${fixed},${fixed},${parent},0,NULL,'',NULL,'closed','closed',false,NULL,NULL,'author',1,1)`);await db.execute(sql`INSERT INTO content_password_credentials (entry_id,credential_version,credential,updated_at) VALUES (${childId},'cred-v1','unused-hash',${fixed})`);return db;}

describe('content model route adapter',()=>{
  it('binds password proofs to both entry and credential version',async()=>{const proof=await issueContentProof(secret,childId,'cred-v1');expect(await verifyContentProof(secret,childId,'cred-v1',proof)).toBe(true);expect(await verifyContentProof(secret,parentId,'cred-v1',proof)).toBe(false);expect(await verifyContentProof(secret,childId,'cred-v2',proof)).toBe(false);expect(await verifyContentProof('other-secret',childId,'cred-v1',proof)).toBe(false);});
  it('resolves a hierarchical route with a custom published status without leaking protected body',async()=>{const db=await fixture(),url=new URL('https://cms.example/books/parent/child');const locked=await resolveContentModelPublicRouteFromDb(db as never,secret,url);expect(locked.resolution.kind).toBe('single');if(locked.resolution.kind!=='single')throw new Error('expected single');expect(locked.resolution.content.access).toBe('passwordRequired');expect('body' in locked.resolution.content.entry).toBe(false);const proof=await issueContentProof(secret,childId,'cred-v1'),open=await resolveContentModelPublicRouteFromDb(db as never,secret,url,proof);expect(open.resolution.kind).toBe('single');if(open.resolution.kind!=='single'||open.resolution.content.access!=='granted')throw new Error('expected granted single');expect(open.resolution.content.entry.body).toBe('Secret body');expect(resolveContentModelRouteTemplate(open)).toEqual({templateKey:'default'});});
  it('rejects the wrong hierarchy and exposes archive/feed route decisions canonically',async()=>{const db=await fixture();expect((await resolveContentModelPublicRouteFromDb(db as never,secret,new URL('https://cms.example/books/wrong/child'))).resolution.kind).toBe('notFound');expect((await resolveContentModelPublicRouteFromDb(db as never,secret,new URL('https://cms.example/books'))).resolution).toMatchObject({kind:'archive',match:{typeKey:'book',page:1}});expect((await resolveContentModelPublicRouteFromDb(db as never,secret,new URL('https://cms.example/books/feed/page/2'))).resolution).toMatchObject({kind:'feed',match:{typeKey:'book',page:2}});});
});
