import { describe, expect, it } from 'vitest'
import { hashPassword, shouldUpgradeHash, verifyPassword, type PepperBinding } from './password.js'
import { hashPassword as hashUtil } from '@platform-modules/util/password'

const pepper: PepperBinding = {
  currentVersion: 'v1',
  secrets: { v1: 'cf-binding-pepper-v1', v2: 'cf-binding-pepper-v2' },
}

describe('hashPassword + verifyPassword (P1a)', () => {
  it('round-trips with after-KDF pepper', async () => {
    const stored = await hashPassword('hunter2', pepper)
    expect(stored.startsWith('pbkdf2sha512-pep$v1$')).toBe(true)
    await expect(verifyPassword('hunter2', stored, pepper)).resolves.toBe(true)
  })

  it('rejects a util digest computed without pepper', async () => {
    const utilOnly = await hashUtil('hunter2')
    await expect(verifyPassword('hunter2', utilOnly, pepper)).resolves.toBe(false)
  })

  it('verifies stored iter from the string after an iteration bump (data-loss floor)', async () => {
    const salt = crypto.getRandomValues(new Uint8Array(16))
    const keyMaterial = await crypto.subtle.importKey(
      'raw',
      new TextEncoder().encode('legacy-iter'),
      'PBKDF2',
      false,
      ['deriveBits'],
    )
    const derived = await crypto.subtle.deriveBits(
      { name: 'PBKDF2', salt, iterations: 50_000, hash: 'SHA-512' },
      keyMaterial,
      64 * 8,
    )
    const key = await crypto.subtle.importKey(
      'raw',
      new TextEncoder().encode(pepper.secrets.v1!),
      { name: 'HMAC', hash: 'SHA-256' },
      false,
      ['sign'],
    )
    const sig = await crypto.subtle.sign('HMAC', key, derived)
    const hmacB64 = btoa(String.fromCharCode(...new Uint8Array(sig)))
    const saltB64 = btoa(String.fromCharCode(...salt))
    const stored = `pbkdf2sha512-pep$v1$50000$${saltB64}$${hmacB64}`
    await expect(verifyPassword('legacy-iter', stored, pepper)).resolves.toBe(true)
  })

  it('keeps prior pepper version verifying after a version bump', async () => {
    const storedV1 = await hashPassword('rotate-me', pepper)
    const pepperV2: PepperBinding = { currentVersion: 'v2', secrets: pepper.secrets }
    const storedV2 = await hashPassword('rotate-me', pepperV2)
    await expect(verifyPassword('rotate-me', storedV1, pepperV2)).resolves.toBe(true)
    await expect(verifyPassword('rotate-me', storedV2, pepperV2)).resolves.toBe(true)
    await expect(verifyPassword('rotate-me', storedV1, { currentVersion: 'v1', secrets: { v1: 'wrong' } })).resolves.toBe(false)
  })
})

describe('shouldUpgradeHash', () => {
  it('returns true for stale pepper version at canonical iterations', async () => {
    const stored = await hashPassword('pw', pepper) // v1 hash
    expect(shouldUpgradeHash(stored, 'v2')).toBe(true)
  })

  it('returns false when pepper version is already current', async () => {
    const stored = await hashPassword('pw', pepper)
    expect(shouldUpgradeHash(stored, 'v1')).toBe(false)
  })

  it('returns false for a hash with higher-than-canonical iterations (never downgrade)', () => {
    // Manually construct a hash with 200k iterations — simulates an import from a stronger system.
    const highIterHash = 'pbkdf2sha512-pep$v1$200000$AAAAAAAAAAAAAAAAAAAAAA==$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA='
    expect(shouldUpgradeHash(highIterHash, 'v2')).toBe(false)
  })

  it('returns false for a malformed or non-pepper hash', () => {
    expect(shouldUpgradeHash('$2b$12$bcrypt-hash', 'v2')).toBe(false)
    expect(shouldUpgradeHash('', 'v2')).toBe(false)
    expect(shouldUpgradeHash('pbkdf2sha512-pep$v1$notanumber$salt$hmac', 'v2')).toBe(false)
  })

  it('upgrades a same-version hash below canonical iterations; never downgrades above-canonical', () => {
    const mk = (ver: string, iter: number) => `pbkdf2sha512-pep$${ver}$${iter}$c2FsdA==$aG1hYw==`
    expect(shouldUpgradeHash(mk('v1', 50_000), 'v1')).toBe(true)
    expect(shouldUpgradeHash(mk('v1', 100_000), 'v1')).toBe(false)
    expect(shouldUpgradeHash(mk('v1', 100_000), 'v2')).toBe(true)
    expect(shouldUpgradeHash(mk('v1', 200_000), 'v2')).toBe(false)
    expect(shouldUpgradeHash(mk('v1', 200_000), 'v1')).toBe(false)
  })
})
