/**
 * Shard-bucket resolution for entity ids.
 *
 * `getShardId` buckets every entity id into one of 256 dirs (00-ff). It must
 * accept BOTH UUID-format ids (bucketed by first byte — the original on-disk
 * layout) AND arbitrary application ids like `'user-123'` (bucketed by a stable
 * hash). A refactor once required UUID format and threw on everything else,
 * which broke the documented `add({ id: '…' })` contract. These tests pin the
 * dual scheme, determinism, and distribution.
 */
import { describe, it, expect } from 'vitest'
import { getShardId, getAllShardIds } from '../../../src/storage/sharding.js'

describe('getShardId', () => {
  it('buckets a UUID by its first byte (with or without hyphens)', () => {
    expect(getShardId('ab123456-1234-5678-9abc-def012345678')).toBe('ab')
    expect(getShardId('ab123456123456789abcdef012345678')).toBe('ab')
    expect(getShardId('CD987654-4321-8765-cba9-fed543210987')).toBe('cd') // case-insensitive
    expect(getShardId('00000000-0000-0000-0000-000000000000')).toBe('00')
  })

  it('buckets an arbitrary application id without throwing (the regression)', () => {
    const shard = getShardId('user-12345')
    expect(shard).toMatch(/^[0-9a-f]{2}$/)
  })

  it('is deterministic for application ids', () => {
    expect(getShardId('order-42')).toBe(getShardId('order-42'))
    expect(getShardId('alice@example.com')).toBe(getShardId('alice@example.com'))
  })

  it('distributes application ids across many buckets (not one hot bucket)', () => {
    const buckets = new Set<string>()
    for (let i = 0; i < 1000; i++) {
      buckets.add(getShardId(`entity-${i}`))
    }
    // FNV-1a over 1000 sequential keys should touch a large fraction of the
    // 256-bucket space; a degenerate hash would collapse to a handful.
    expect(buckets.size).toBeGreaterThan(200)
  })

  it('every produced shard is a valid bucket id', () => {
    const valid = new Set(getAllShardIds())
    for (const sample of ['user-1', 'café', '🚀', 'a', 'A_VERY_LONG_APPLICATION_KEY_0001']) {
      expect(valid.has(getShardId(sample))).toBe(true)
    }
  })

  it('throws on an empty id', () => {
    expect(() => getShardId('')).toThrow(/id is required/)
  })
})
