import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { sql } from 'drizzle-orm'
import { makePgHarness } from './pg-harness.js'
import { fieldsMigrationSql } from './migrate.js'
import { defineFieldGroup, resolveGroups } from './registry.js'
import { getEntityValues, setEntityValues, deleteEntityValues, createGroup, updateGroup, deleteGroup, listGroups } from './store.js'
import { isFieldAuthorizationError, isFieldGroupConflictError, isFieldStoreError } from './errors.js'

const editor = { id: 'u', canEditFields: true, canManageGroups: true }
const code = defineFieldGroup({
  key: 'attrs', label: 'Attrs', location: { entityType: 'product' },
  fields: [
    { type: 'text', key: 'material', label: 'Material' },
    { type: 'number', key: 'weight', label: 'Weight' },
    { type: 'boolean', key: 'featured', label: 'Featured' },
    { type: 'select', key: 'tags', label: 'Tags', options: [{ value: 'a', label: 'A' }, { value: 'b', label: 'B' }], multiple: true },
  ],
})
const h = await makePgHarness()
beforeAll(async () => {
  for (const s of fieldsMigrationSql()
    .split(';')
    .map((x) => x.trim())
    .filter(Boolean)) {
    await h.db.execute(sql.raw(s))
  }
})
afterAll(async () => { await h.teardown() })
const ref = { entityType: 'product', entityId: 'p1' }
const resolved = async () => (await resolveGroups(h.db, { entityType: 'product', codeGroups: [code] })).find((g) => g.key === 'attrs')!

describe('store values', () => {
  it('set → get round-trips every lane incl multi ordinal', async () => {
    await setEntityValues(h.db, editor, { ref, groupId: 'attrs', resolved: await resolved(), values: { material: 'leather', weight: 12, featured: true, tags: ['a', 'b'] } })
    const got = await getEntityValues(h.db, ref, { entityType: 'product', codeGroups: [code] })
    expect(got.material).toBe('leather'); expect(Number(got.weight)).toBe(12); expect(got.featured).toBe(true)
    expect(got.tags).toEqual(['a', 'b'])
  })
  it('set is a replace (delete-set + insert-set)', async () => {
    await setEntityValues(h.db, editor, { ref, groupId: 'attrs', resolved: await resolved(), values: { material: 'suede' } })
    const got = await getEntityValues(h.db, ref, { entityType: 'product', codeGroups: [code] })
    expect(got.material).toBe('suede'); expect(got.weight).toBeUndefined()
  })
  it('rejects without canEditFields', async () => {
    await expect(setEntityValues(h.db, { id: 'x' }, { ref, groupId: 'attrs', resolved: await resolved(), values: { material: 'x' } }))
      .rejects.toSatisfy(isFieldAuthorizationError)
  })
  it('deleteEntityValues removes all rows for the ref', async () => {
    await deleteEntityValues(h.db, ref)
    const got = await getEntityValues(h.db, ref, { entityType: 'product', codeGroups: [code] })
    expect(Object.keys(got).length).toBe(0)
  })
})
describe('atomicity', () => {
  it('rolls the delete-set back when the insert-set faults (partial-failure)', async () => {
    const r2 = { entityType: 'product', entityId: 'pAtomic' }
    await setEntityValues(h.db, editor, { ref: r2, groupId: 'attrs', resolved: await resolved(), values: { material: 'leather', weight: 12 } })
    // NUL byte passes app validation (typeof string) but Postgres text cannot store 0x00 → INSERT faults after the delete.
    const nulBearing = `bad${String.fromCharCode(0)}value`
    await expect(setEntityValues(h.db, editor, { ref: r2, groupId: 'attrs', resolved: await resolved(), values: { material: nulBearing } }))
      .rejects.toSatisfy(isFieldStoreError)
    const got = await getEntityValues(h.db, r2, { entityType: 'product', codeGroups: [code] })
    expect(got.material).toBe('leather'); expect(Number(got.weight)).toBe(12)
  })
})
describe('group CRUD', () => {
  it('createGroup rejects a key that shadows a code group', async () => {
    await expect(createGroup(h.db, editor, { key: 'attrs', label: 'X', location: { entityType: 'product' }, fields: [] }, [code]))
      .rejects.toSatisfy(isFieldGroupConflictError)
  })
  it('updateGroup patches and persists', async () => {
    const created = await createGroup(h.db, editor, { key: 'specs', label: 'Specs', location: { entityType: 'doc' }, fields: [{ type: 'text', key: 'isbn', label: 'ISBN' }] })
    const updated = await updateGroup(h.db, editor, created.id!, { label: 'Specifications', position: 3 })
    expect(updated.label).toBe('Specifications'); expect(updated.position).toBe(3); expect(updated.id).toBe(created.id)
    const listed = (await listGroups(h.db, { entityType: 'doc' })).find((g) => g.id === created.id)!
    expect(listed.label).toBe('Specifications')
  })
  it('deleteGroup removes the row', async () => {
    const created = await createGroup(h.db, editor, { key: 'tmp', label: 'Tmp', location: { entityType: 'gone' }, fields: [] })
    await deleteGroup(h.db, editor, created.id!)
    expect(await listGroups(h.db, { entityType: 'gone' })).toEqual([])
  })
  it('listGroups filters by entityType', async () => {
    await createGroup(h.db, editor, { key: 'only', label: 'Only', location: { entityType: 'widget' }, fields: [] })
    const rows = await listGroups(h.db, { entityType: 'widget' })
    expect(rows.every((g) => g.location.entityType === 'widget')).toBe(true)
    expect(rows.some((g) => g.key === 'only')).toBe(true)
  })
})
