import { afterEach, describe, expect, it } from 'vitest'
import { sql } from 'drizzle-orm'
import { makePgHarness } from './pg-harness.js'
import { canonicalFieldGroupHash, type FieldGroup } from './schema.js'
import { fieldsMigrationSql, fieldsRecursiveEavForwardMigrationSql } from './migrate.js'
import { fieldsRuntimeMigrationSql } from './revisions.js'
import {
  createOptionsPage,
  defineOptionsPage,
  getOptionsValues,
  previewOptionsPageDeactivation,
  applyOptionsPageDeactivation,
  resolveOptionsPages,
  setOptionsValues,
  updateOptionsPage,
} from './options.js'
import type { FieldsAuthorization, FieldsContext, FieldsPrincipal } from './types.js'

const group: FieldGroup = Object.freeze({
  key: 'options_profile',
  title: 'Options Profile',
  active: true,
  location: Object.freeze([Object.freeze([{ parameter: 'optionsPage', operator: 'eq' as const, value: 'site' }])]),
  fields: Object.freeze([
    Object.freeze({ key: 'headline', name: 'headline', label: 'Headline', type: 'text', settings: Object.freeze({ maxLength: 80 }) }),
  ]),
})
const definition = Object.freeze({
  key: 'site', title: 'Site Settings', readCapability: 'options.read', writeCapability: 'options.write', manageCapability: 'options.manage', position: 10,
})

let teardown: (() => Promise<void>) | undefined
afterEach(async () => { await teardown?.(); teardown = undefined })

function context(capabilities: readonly string[] = ['options.read','options.write','options.manage']): FieldsContext {
  const principal: FieldsPrincipal = Object.freeze({ id: 'admin-1', capabilities: new Set(capabilities) })
  const authorization: FieldsAuthorization = Object.freeze({ assert: () => undefined, assertDefinitionMigration: () => ({ policyVersion: 'policy-1' }) })
  return Object.freeze({ principal, authorization })
}
async function fixture() {
  const harness = await makePgHarness(); teardown = harness.teardown
  for (const statement of fieldsMigrationSql().split(';').map((item) => item.trim()).filter(Boolean)) await harness.db.execute(sql.raw(statement))
  for (const statement of fieldsRecursiveEavForwardMigrationSql('postgres')) await harness.db.execute(sql.raw(statement))
  for (const statement of fieldsRuntimeMigrationSql('postgres')) await harness.db.execute(sql.raw(statement))
  const hash = await canonicalFieldGroupHash(group)
  const now = new Date('2026-08-22T00:00:00.000Z').toISOString()
  await harness.db.execute(sql`INSERT INTO field_group_definitions (key,origin,version,active,current_revision,canonical_hash,definition,created_at,updated_at) VALUES (${group.key},'db',1,true,1,${hash},${JSON.stringify(group)},${now},${now})`)
  await harness.db.execute(sql`INSERT INTO field_definition_versions (group_key,revision,canonical_hash,definition,origin,created_at) VALUES (${group.key},1,${hash},${JSON.stringify(group)},'db',${now})`)
  return harness
}

describe('options pages', () => {
  it('resolves code as authoritative while retaining DB collision metadata', async () => {
    const { db } = await fixture()
    await db.transaction((tx) => createOptionsPage(tx, definition, context(), { operationId: 'options:create' }))
    const code = defineOptionsPage({ ...definition, title: 'Code Settings' })
    const resolved = await resolveOptionsPages(db, { codePages: [code] })
    expect(resolved).toEqual([expect.objectContaining({ key: 'site', title: 'Code Settings', origin: 'code', shadowedDbVersion: 1 })])
  })

  it('creates and updates immutable definition revisions with exact operation replay', async () => {
    const { db } = await fixture()
    const created = await db.transaction((tx) => createOptionsPage(tx, definition, context(), { operationId: 'options:create' }))
    const replay = await db.transaction((tx) => createOptionsPage(tx, definition, context(), { operationId: 'options:create' }))
    expect(replay).toEqual(created)

    const updated = await db.transaction((tx) => updateOptionsPage(tx, 'site', { title: 'Changed' }, 1, context(), { operationId: 'options:update' }))
    const updateReplay = await db.transaction((tx) => updateOptionsPage(tx, 'site', { title: 'Changed' }, 1, context(), { operationId: 'options:update' }))
    expect(updateReplay).toEqual(updated)
    expect(updated).toMatchObject({ version: 2, title: 'Changed' })
    const versions = await db.transaction((tx) => tx.execute<{ revision: number; definition: unknown }>(sql`SELECT revision,definition FROM field_options_definition_versions WHERE options_key='site' ORDER BY revision`))
    expect(versions.map((row) => row.revision)).toEqual([1,2])
  })

  it('uses separate read/write capabilities, recursive EAV versions, and exact write replay', async () => {
    const { db } = await fixture()
    await db.transaction((tx) => createOptionsPage(tx, definition, context(), { operationId: 'options:create' }))
    const empty = await getOptionsValues(db, 'site', context())
    const result = await db.transaction((tx) => setOptionsValues(tx, 'site', { options_profile: { headline: 'Hello' } }, { expectedVersion: empty.version, operationId: 'options:write' }, context()))
    const replay = await db.transaction((tx) => setOptionsValues(tx, 'site', { options_profile: { headline: 'Hello' } }, { expectedVersion: empty.version, operationId: 'options:write' }, context()))
    expect(replay.version).toBe(result.version)
    expect((await getOptionsValues(db, 'site', context())).values).toEqual({ options_profile: { headline: 'Hello' } })
    await expect(getOptionsValues(db, 'site', context(['options.write','options.manage']))).rejects.toMatchObject({ code: 'access-denied' })
    await expect(db.transaction((tx) => setOptionsValues(tx, 'site', { options_profile: { headline: 'Denied' } }, { expectedVersion: result.version, operationId: 'options:denied' }, context(['options.read','options.manage'])))).rejects.toMatchObject({ code: 'access-denied' })
  })

  it('binds deactivation to the value corpus and supports authorized exact replay', async () => {
    const { db } = await fixture()
    await db.transaction((tx) => createOptionsPage(tx, definition, context(), { operationId: 'options:create' }))
    const empty = await getOptionsValues(db, 'site', context())
    await db.transaction((tx) => setOptionsValues(tx, 'site', { options_profile: { headline: 'Bound' } }, { expectedVersion: empty.version, operationId: 'options:write' }, context()))
    const impact = await previewOptionsPageDeactivation(db, 'site', 1, context())
    const applied = await db.transaction((tx) => applyOptionsPageDeactivation(tx, impact.token, { kind: 'retain' }, 'options:deactivate', context()))
    const replay = await db.transaction((tx) => applyOptionsPageDeactivation(tx, impact.token, { kind: 'retain' }, 'options:deactivate', context()))
    expect(applied).toMatchObject({ active: false, version: 2 })
    expect(replay).toEqual(applied)
  })
})
