import { and, desc, eq, lt } from 'drizzle-orm'
import type { Querier } from '@platform-modules/db'
import { assertCanModify, type Actor } from './authz.js'
import type { ContentEntry, ContentRevision, RevisionListQuery, RevisionPage, Sanitize } from './model.js'
import { contentEntries, contentRevisions, type ContentSchema } from './schema.js'
import { ContentNotFoundError, getById, put } from './store.js'
import { assignTerms, termsForEntry } from './taxonomy.js'

type RevisionRow = typeof contentRevisions.$inferSelect
type EntryRow = typeof contentEntries.$inferSelect

/** Idempotent DDL for adopters migrating existing content_entries tables (spec §8). */
export const contentRevisionsMigrationSql = () => `
CREATE TABLE IF NOT EXISTS content_revisions (
  id         uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  entry_id   uuid NOT NULL REFERENCES content_entries(id) ON DELETE CASCADE,
  seq        bigserial NOT NULL,
  title      text NOT NULL,
  body       text NOT NULL,
  slug       text NOT NULL,
  type       text NOT NULL,
  term_ids   jsonb NOT NULL DEFAULT '[]'::jsonb,
  editor     text NOT NULL,
  created_at timestamptz(3) NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS content_revisions_entry_seq_idx ON content_revisions (entry_id, seq DESC);
`.trim()

async function getRow(db: Querier<ContentSchema>, id: string): Promise<EntryRow> {
  const [row] = await db.select().from(contentEntries).where(eq(contentEntries.id, id)).limit(1)
  if (!row) throw new ContentNotFoundError(`id=${id}`)
  return row
}

function toRevision(row: RevisionRow): ContentRevision {
  return {
    id: row.id,
    entryId: row.entryId,
    seq: row.seq,
    title: row.title,
    body: row.body,
    slug: row.slug,
    type: row.type,
    termIds: row.termIds ?? [],
    editor: row.editor,
    createdAt: row.createdAt,
  }
}

/** Append a snapshot of the entry's current live state before a put()-update (spec §4.1). */
export async function snapshotRevision(
  db: Querier<ContentSchema>,
  entryId: string,
  actor: Actor,
): Promise<ContentRevision> {
  const existing = await getRow(db, entryId)
  assertCanModify(actor, 'update', existing.author, existing.id)
  const termIds = (await termsForEntry(db, existing.id)).map((t) => t.id)
  const [inserted] = await db
    .insert(contentRevisions)
    .values({
      entryId: existing.id,
      title: existing.title,
      body: existing.body,
      slug: existing.slug,
      type: existing.type,
      termIds,
      editor: actor.id,
    })
    .returning()
  return toRevision(inserted!)
}

const DEFAULT_LIMIT = 20
const MAX_LIMIT = 100

function clampLimit(limit?: number): number {
  return Math.min(Math.max(limit ?? DEFAULT_LIMIT, 1), MAX_LIMIT)
}

/** Keyset-paginated revision history for one entry, newest first (spec §4.2). */
export async function listRevisions(
  db: Querier<ContentSchema>,
  entryId: string,
  actor: Actor,
  query: RevisionListQuery = {},
): Promise<RevisionPage> {
  const existing = await getRow(db, entryId)
  assertCanModify(actor, 'read', existing.author, existing.id)
  const limit = clampLimit(query.limit)
  const filters = [eq(contentRevisions.entryId, entryId)]
  if (query.before !== undefined) filters.push(lt(contentRevisions.seq, query.before))
  const rows = await db
    .select()
    .from(contentRevisions)
    .where(and(...filters))
    .orderBy(desc(contentRevisions.seq))
    .limit(limit + 1)
  const hasMore = rows.length > limit
  const trimmed = hasMore ? rows.slice(0, limit) : rows
  return {
    revisions: trimmed.map(toRevision),
    nextCursor: hasMore ? trimmed[trimmed.length - 1]!.seq : null,
  }
}

/** Load one revision by id (spec §4.3). */
export async function getRevision(
  db: Querier<ContentSchema>,
  revisionId: string,
  actor: Actor,
): Promise<ContentRevision> {
  const [row] = await db.select().from(contentRevisions).where(eq(contentRevisions.id, revisionId)).limit(1)
  if (!row) throw new ContentNotFoundError(`revision id=${revisionId}`)
  const parent = await getRow(db, row.entryId)
  assertCanModify(actor, 'read', parent.author, parent.id)
  return toRevision(row)
}

/** Restore a revision's content tuple via put(); snapshots current state first (spec §4.4). */
export async function restoreRevision(
  db: Querier<ContentSchema>,
  revisionId: string,
  actor: Actor,
  sanitize: Sanitize,
): Promise<ContentEntry> {
  const revision = await getRevision(db, revisionId, actor)
  const parent = await getRow(db, revision.entryId)
  assertCanModify(actor, 'update', parent.author, parent.id)
  await snapshotRevision(db, parent.id, actor)
  await put(
    db,
    {
      id: parent.id,
      slug: revision.slug,
      type: revision.type,
      title: revision.title,
      body: revision.body,
    },
    actor,
    sanitize,
  )
  await assignTerms(db, parent.id, revision.termIds, actor)
  return (await getById(db, parent.id, actor))!
}
