import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { eq, sql } from 'drizzle-orm'
import { makePgHarness } from './pg-harness.js'
import { menusMigrationSql, menuItems } from './schema.js'
import { createMenu } from './menus.js'
import { addMenuItem } from './items.js'
import { getMenuTree, listMenus } from './read.js'

const admin = { id: 'admin', canManageMenus: true }
const h = await makePgHarness()

beforeAll(async () => {
  for (const s of menusMigrationSql()
    .split(';')
    .map((x) => x.trim())
    .filter(Boolean)) {
    await h.db.execute(sql.raw(s))
  }
})
afterAll(async () => {
  await h.teardown()
})

describe('listMenus', () => {
  it('lists menus without authz', async () => {
    await createMenu(h.db, admin, { key: 'list-a', label: 'A' })
    await createMenu(h.db, admin, { key: 'list-b', label: 'B' })
    const menus = await listMenus(h.db)
    expect(menus.map((m) => m.key).sort()).toEqual(['list-a', 'list-b'])
  })
})

describe('getMenuTree', () => {
  it('returns a nested tree ordered by position with no actor', async () => {
    const menu = await createMenu(h.db, admin, { key: 'tree-menu', label: 'Tree' })
    const second = await addMenuItem(h.db, admin, menu.id, {
      label: 'Second',
      target: { kind: 'url', url: '/second' },
    })
    const first = await addMenuItem(h.db, admin, menu.id, {
      label: 'First',
      target: { kind: 'url', url: '/first' },
    })
    await h.db
      .update(menuItems)
      .set({ position: 0 })
      .where(eq(menuItems.id, first.id))
    await h.db
      .update(menuItems)
      .set({ position: 1 })
      .where(eq(menuItems.id, second.id))
    const child = await addMenuItem(h.db, admin, menu.id, {
      parentId: first.id,
      label: 'Child',
      target: { kind: 'url', url: '/child' },
    })

    const tree = await getMenuTree(h.db, 'tree-menu')
    expect(tree?.menu.key).toBe('tree-menu')
    expect(tree?.items.map((n) => n.label)).toEqual(['First', 'Second'])
    expect(tree?.items[0]?.children.map((n) => n.id)).toEqual([child.id])
  })

  it('terminates on a corrupt parent loop via visited-set', async () => {
    const menu = await createMenu(h.db, admin, { key: 'loop-menu', label: 'Loop' })
    const ts = Date.now()
    const aId = crypto.randomUUID()
    const bId = crypto.randomUUID()
    await h.db.insert(menuItems).values([
      {
        id: aId,
        menuId: menu.id,
        parentId: bId,
        position: 0,
        depth: 0,
        label: 'A',
        targetKind: 'url',
        url: '/a',
        targetEntityType: null,
        targetEntityId: null,
        openInNew: false,
        createdAtMs: ts,
        updatedAtMs: ts,
      },
      {
        id: bId,
        menuId: menu.id,
        parentId: aId,
        position: 1,
        depth: 0,
        label: 'B',
        targetKind: 'url',
        url: '/b',
        targetEntityType: null,
        targetEntityId: null,
        openInNew: false,
        createdAtMs: ts,
        updatedAtMs: ts,
      },
    ])

    const tree = await getMenuTree(h.db, 'loop-menu')
    expect(tree?.items.length).toBeGreaterThan(0)
    const totalNodes = tree!.items.reduce((acc, n) => acc + 1 + n.children.length, 0)
    expect(totalNodes).toBeLessThanOrEqual(2)
  })
})
