import { describe, expect, it } from 'vitest'
import { InvalidEntityIdError, InvalidFieldKeyError } from './errors.js'
import { assertEntityId, assertEntityType, assertFieldKey } from './validate.js'

const VALID_KEYS = ['title', 'field_key', 'a', 'abc123'] as const

const INVALID_KEYS = [
  { label: 'empty string', value: '' },
  { label: 'whitespace', value: ' ' },
  { label: 'dots', value: '..' },
  { label: 'uppercase first char', value: 'Title' },
  { label: 'underscore first char', value: '_key' },
  { label: 'digit first char', value: '123abc' },
  { label: 'hyphen', value: 'field-key' },
  { label: 'space in middle', value: 'field key' },
] as const

function assertValid(fn: (key: string) => void): void {
  for (const key of VALID_KEYS) {
    it(`accepts '${key}'`, () => {
      expect(() => fn(key)).not.toThrow()
    })
  }

  it('accepts 65+ char all-lowercase string (no length cap in regex)', () => {
    expect(() => fn('a'.repeat(65))).not.toThrow()
  })
}

function assertInvalid(fn: (key: string) => void): void {
  for (const { label, value } of INVALID_KEYS) {
    it(`rejects ${label}: '${value}'`, () => {
      expect(() => fn(value)).toThrow(InvalidFieldKeyError)
    })
  }
}

describe('assertFieldKey', () => {
  assertValid(assertFieldKey)
  assertInvalid(assertFieldKey)
})

describe('assertEntityType', () => {
  assertValid(assertEntityType)
  assertInvalid(assertEntityType)
})

describe('assertEntityId', () => {
  it('rejects empty string', () => {
    expect(() => assertEntityId('')).toThrow(InvalidEntityIdError)
  })

  it('rejects string longer than 1024 chars', () => {
    expect(() => assertEntityId('a'.repeat(1025))).toThrow(InvalidEntityIdError)
  })

  it('rejects string with null byte', () => {
    expect(() => assertEntityId('product\0-123')).toThrow(InvalidEntityIdError)
  })

  it('accepts valid entity id', () => {
    expect(() => assertEntityId('product-123')).not.toThrow()
  })

  it('accepts valid UUID', () => {
    expect(() => assertEntityId('123e4567-e89b-12d3-a456-426614174000')).not.toThrow()
  })
})
