import { sql } from 'drizzle-orm'
import { beforeEach, describe, expect, it } from 'vitest'
import type { Querier } from '@platform-modules/db'
import { createPgliteClient } from '../../db/src/postgres/pglite.js'
import { ContentAuthzError, type Actor } from './authz.js'
import { contentRevisionsMigrationSql } from './revisions.js'
import { contentSchema, type ContentSchema } from './schema.js'
import {
  TermConflictError,
  TermValidationError,
  contentTaxonomyMigrationSql,
  createTerm,
  listTerms,
  updateTerm,
} from './taxonomy.js'

const CREATE_ENTRIES = sql`
  CREATE TABLE content_entries (
    id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
    slug text NOT NULL,
    type text NOT NULL,
    title text NOT NULL,
    body text NOT NULL DEFAULT '',
    status text NOT NULL DEFAULT 'draft',
    visibility text NOT NULL DEFAULT 'public',
    published_at timestamptz(3),
    author text NOT NULL,
    created_at timestamptz(3) NOT NULL DEFAULT NOW(),
    updated_at timestamptz(3) NOT NULL DEFAULT NOW()
  )
`

const CREATE_INDEX = sql`
  CREATE UNIQUE INDEX content_entries_type_slug_uq ON content_entries (type, slug)
`

const manager: Actor = { id: 'mgr-1', canManageTaxonomy: true }
const stranger: Actor = { id: 'stranger' }

async function db(): Promise<Querier<ContentSchema>> {
  const handle = createPgliteClient({ schema: contentSchema })
  await handle.execute(CREATE_ENTRIES)
  await handle.execute(CREATE_INDEX)
  for (const s of contentRevisionsMigrationSql()
    .split(';')
    .map((x) => x.trim())
    .filter(Boolean)) {
    await handle.execute(sql.raw(s))
  }
  for (const s of contentTaxonomyMigrationSql()
    .split(';')
    .map((x) => x.trim())
    .filter(Boolean)) {
    await handle.execute(sql.raw(s))
  }
  return handle as unknown as Querier<ContentSchema>
}

describe('createTerm / updateTerm / listTerms', () => {
  let d: Querier<ContentSchema>
  beforeEach(async () => {
    d = await db()
  })

  it('creates root (depth 0) and child (depth 1)', async () => {
    const root = await createTerm(d, { taxonomy: 'category', slug: 'news', name: 'News' }, manager)
    expect(root.depth).toBe(0)
    expect(root.parentId).toBeNull()

    const child = await createTerm(
      d,
      { taxonomy: 'category', slug: 'local', name: 'Local', parentId: root.id },
      manager,
    )
    expect(child.depth).toBe(1)
    expect(child.parentId).toBe(root.id)
  })

  it('rejects depth beyond MAX_TERM_DEPTH', async () => {
    let parentId: string | null = null
    for (let i = 0; i <= 6; i++) {
      const t = await createTerm(
        d,
        { taxonomy: 'category', slug: `d${i}`, name: `D${i}`, parentId: parentId ?? undefined },
        manager,
      )
      parentId = t.id
    }
    await expect(
      createTerm(d, { taxonomy: 'category', slug: 'too-deep', name: 'Too deep', parentId: parentId! }, manager),
    ).rejects.toBeInstanceOf(TermValidationError)
  })

  it('rejects duplicate sibling slug with TermConflictError', async () => {
    await createTerm(d, { taxonomy: 'tag', slug: 'dup', name: 'A' }, manager)
    await expect(createTerm(d, { taxonomy: 'tag', slug: 'dup', name: 'B' }, manager)).rejects.toBeInstanceOf(
      TermConflictError,
    )
  })

  it('conflicts on duplicate root slugs but allows root slug vs child slug under a parent', async () => {
    await createTerm(d, { taxonomy: 'tag', slug: 'shared', name: 'Root' }, manager)
    await expect(createTerm(d, { taxonomy: 'tag', slug: 'shared', name: 'Root2' }, manager)).rejects.toBeInstanceOf(
      TermConflictError,
    )

    const parent = await createTerm(d, { taxonomy: 'tag', slug: 'parent', name: 'Parent' }, manager)
    const child = await createTerm(
      d,
      { taxonomy: 'tag', slug: 'shared', name: 'Child', parentId: parent.id },
      manager,
    )
    expect(child.slug).toBe('shared')
  })

  it('updateTerm rename keeps id stable and re-checks sibling uniqueness', async () => {
    const t = await createTerm(d, { taxonomy: 'category', slug: 'a', name: 'A' }, manager)
    const renamed = await updateTerm(d, t.id, { name: 'Alpha', slug: 'alpha' }, manager)
    expect(renamed.id).toBe(t.id)
    expect(renamed.name).toBe('Alpha')
    expect(renamed.slug).toBe('alpha')
  })

  it('denies non-manage actor', async () => {
    await expect(createTerm(d, { taxonomy: 'tag', slug: 'x', name: 'X' }, stranger)).rejects.toBeInstanceOf(
      ContentAuthzError,
    )
  })

  it('listTerms returns flat list ordered by depth then name (tree-buildable)', async () => {
    const b = await createTerm(d, { taxonomy: 'category', slug: 'b', name: 'B' }, manager)
    const a = await createTerm(d, { taxonomy: 'category', slug: 'a', name: 'A' }, manager)
    const child = await createTerm(d, { taxonomy: 'category', slug: 'c', name: 'C', parentId: a.id }, manager)

    const listed = await listTerms(d, 'category')
    expect(listed.map((t) => t.id)).toEqual([a.id, b.id, child.id])
    expect(listed.every((t) => t.taxonomy === 'category')).toBe(true)
  })
})
