import { describe, expect, it } from 'vitest'
import { generateTemporaryPassword, hashPassword, passwordProblem, verifyPassword } from '@/lib/auth/passwords'
import { emailPattern, escapeLike } from '@/lib/like'
import { inChunks, IN_CHUNK_SIZE } from '@/lib/data/chunked'
import { canSeeAllTrips } from '@/lib/data/trips'

/**
 * 2026-09-11 requirements: real password reset (1) and data access (3).
 * These pieces decide who can sign in and who can see which trip, so their
 * rules are pinned here rather than left to manual testing.
 */

describe('password hashing', () => {
  it('verifies the right password and rejects a wrong one', async () => {
    const hash = await hashPassword('Correct-Horse-9')
    expect(hash.startsWith('scrypt$')).toBe(true)
    expect(await verifyPassword('Correct-Horse-9', hash)).toBe(true)
    expect(await verifyPassword('correct-horse-9', hash)).toBe(false)
    expect(await verifyPassword('', hash)).toBe(false)
  })
  it('salts: the same password never hashes the same twice', async () => {
    const [a, b] = await Promise.all([hashPassword('Same-Pass-1'), hashPassword('Same-Pass-1')])
    expect(a).not.toBe(b)
  })
  it('treats any malformed stored hash as a failed login, not an error', async () => {
    expect(await verifyPassword('x', '')).toBe(false)
    expect(await verifyPassword('x', 'plain-text-password')).toBe(false)
    expect(await verifyPassword('x', 'scrypt$1$2$3$bad$bad')).toBe(false)
  })
})

describe('temporary passwords', () => {
  it('are four groups of four unambiguous characters', () => {
    const pw = generateTemporaryPassword()
    expect(pw).toMatch(/^[A-HJ-NP-Za-hj-km-np-z2-9]{4}(-[A-HJ-NP-Za-hj-km-np-z2-9]{4}){3}$/)
    expect(pw).not.toMatch(/[0O1lI]/)
  })
  it('do not repeat', () => {
    const seen = new Set(Array.from({ length: 200 }, generateTemporaryPassword))
    expect(seen.size).toBe(200)
  })
  it('always satisfy the rules for a typed password', () => {
    for (let i = 0; i < 50; i++) expect(passwordProblem(generateTemporaryPassword())).toBeNull()
  })
})

describe('typed password rules', () => {
  it('rejects short and letter-only or digit-only passwords', () => {
    expect(passwordProblem('Short1')).not.toBeNull()
    expect(passwordProblem('onlyletters')).not.toBeNull()
    expect(passwordProblem('1234567890')).not.toBeNull()
    expect(passwordProblem('Good-password-7')).toBeNull()
  })
})

describe('escapeLike — an email matches only itself', () => {
  it('escapes LIKE wildcards', () => {
    expect(escapeLike('test_driver@users.local')).toBe('test\\_driver@users.local')
    expect(escapeLike('100%')).toBe('100\\%')
    expect(escapeLike('a\\b')).toBe('a\\\\b')
  })
  it('leaves an address with no wildcards unchanged', () => {
    expect(emailPattern('  vlad@atlaslogistics.net ')).toBe('vlad@atlaslogistics.net')
  })
})

describe('canSeeAllTrips — admins see everything', () => {
  it('is true only for the admin role', () => {
    expect(canSeeAllTrips({ role: 'admin' })).toBe(true)
    expect(canSeeAllTrips({ role: 'broker' })).toBe(false)
    expect(canSeeAllTrips({ role: 'dispatcher' })).toBe(false)
    expect(canSeeAllTrips({ role: 'driver' })).toBe(false)
  })
})

describe('inChunks — long id lists never overflow a request', () => {
  it('splits into bounded chunks and returns every row once', async () => {
    const ids = Array.from({ length: IN_CHUNK_SIZE * 2 + 7 }, (_, i) => `id-${i}`)
    const sizes: number[] = []
    const { data, error } = await inChunks(ids, async (chunk) => {
      sizes.push(chunk.length)
      return { data: chunk.map((id) => ({ id })), error: null }
    })
    expect(error).toBeNull()
    expect(data).toHaveLength(ids.length)
    expect(Math.max(...sizes)).toBeLessThanOrEqual(IN_CHUNK_SIZE)
    expect(sizes).toHaveLength(3)
  })
  it('de-duplicates ids and stops on the first error', async () => {
    const { data, error } = await inChunks(['a', 'a', 'b'], async () => ({ data: null, error: new Error('boom') }))
    expect(error).toBeInstanceOf(Error)
    expect(data).toHaveLength(0)
  })
})
