/**
 * test/auth.test.ts — unit tests for src/http/auth.ts (w5-access-token).
 *
 * Mirrors the discipline of test/origin.test.ts. Security-critical: the
 * constant-time comparison, the cookie flags, and the disabled short-circuit.
 */

import { describe, it, expect } from 'vitest'
import {
  AUTH_COOKIE_NAME,
  buildSetCookie,
  constantTimeEqual,
  cookieIsAuthed,
  isAuthEnabled,
  isHttpsRequest,
  parseCookieHeader,
} from '../src/http/auth.js'

const TOKEN = 'super-secret-token-1234' // ≥16, safe charset

// ── constantTimeEqual ─────────────────────────────────────────────────────────
describe('constantTimeEqual', () => {
  it('returns true for equal strings', () => {
    expect(constantTimeEqual(TOKEN, TOKEN)).toBe(true)
  })

  it('returns false for unequal same-length strings', () => {
    expect(constantTimeEqual('aaaaaaaaaaaaaaaa', 'aaaaaaaaaaaaaaab')).toBe(false)
  })

  it('returns false for different-length strings without throwing (fixed-length guard)', () => {
    // The SHA-256-to-fixed-32-bytes guard means unequal lengths never throw.
    expect(() => constantTimeEqual('short', 'a-much-longer-candidate-value')).not.toThrow()
    expect(constantTimeEqual('short', 'a-much-longer-candidate-value')).toBe(false)
  })

  it('returns false for an empty candidate (present-vs-absent short-circuit)', () => {
    expect(constantTimeEqual('', TOKEN)).toBe(false)
    expect(constantTimeEqual(TOKEN, '')).toBe(false)
  })

  it('returns false for an undefined candidate', () => {
    expect(constantTimeEqual(undefined, TOKEN)).toBe(false)
    expect(constantTimeEqual(TOKEN, undefined)).toBe(false)
  })
})

// ── parseCookieHeader ─────────────────────────────────────────────────────────
describe('parseCookieHeader', () => {
  it('parses a multi-pair cookie header into a map', () => {
    const map = parseCookieHeader('a=1; webterm_auth=xyz; b=2')
    expect(map).toEqual({ a: '1', webterm_auth: 'xyz', b: '2' })
  })

  it('returns {} for a missing or empty header', () => {
    expect(parseCookieHeader(undefined)).toEqual({})
    expect(parseCookieHeader('')).toEqual({})
  })

  it('ignores malformed pairs (no = / empty name)', () => {
    const map = parseCookieHeader('novalue; =orphan; webterm_auth=ok')
    expect(map).toEqual({ webterm_auth: 'ok' })
  })
})

// ── isAuthEnabled ─────────────────────────────────────────────────────────────
describe('isAuthEnabled', () => {
  it('is false when the token is undefined', () => {
    expect(isAuthEnabled({ webtermToken: undefined })).toBe(false)
  })

  it('is false when the token is an empty string', () => {
    expect(isAuthEnabled({ webtermToken: '' })).toBe(false)
  })

  it('is true when a token is set', () => {
    expect(isAuthEnabled({ webtermToken: TOKEN })).toBe(true)
  })
})

// ── cookieIsAuthed ────────────────────────────────────────────────────────────
describe('cookieIsAuthed', () => {
  it('is true for a correct auth cookie', () => {
    expect(cookieIsAuthed({ webtermToken: TOKEN }, `${AUTH_COOKIE_NAME}=${TOKEN}`)).toBe(true)
  })

  it('is false for a wrong cookie value', () => {
    expect(cookieIsAuthed({ webtermToken: TOKEN }, `${AUTH_COOKIE_NAME}=nope-nope-nope-nope`)).toBe(false)
  })

  it('is false when the auth cookie is absent', () => {
    expect(cookieIsAuthed({ webtermToken: TOKEN }, 'other=1')).toBe(false)
    expect(cookieIsAuthed({ webtermToken: TOKEN }, undefined)).toBe(false)
  })

  it('is false when auth is disabled (no token) regardless of cookie', () => {
    expect(cookieIsAuthed({ webtermToken: undefined }, `${AUTH_COOKIE_NAME}=anything`)).toBe(false)
  })
})

// ── buildSetCookie ────────────────────────────────────────────────────────────
describe('buildSetCookie', () => {
  it('includes the token value and the required flags', () => {
    const c = buildSetCookie({ webtermToken: TOKEN }, { secure: false })
    expect(c).toContain(`${AUTH_COOKIE_NAME}=${TOKEN}`)
    expect(c).toContain('Path=/')
    expect(c).toContain('Max-Age=')
    expect(c).toContain('HttpOnly')
    expect(c).toContain('SameSite=Strict')
  })

  it('omits Secure when opts.secure is false (LAN over http)', () => {
    expect(buildSetCookie({ webtermToken: TOKEN }, { secure: false })).not.toContain('Secure')
  })

  it('includes Secure when opts.secure is true (relay over https)', () => {
    expect(buildSetCookie({ webtermToken: TOKEN }, { secure: true })).toContain('; Secure')
  })
})

// ── isHttpsRequest ────────────────────────────────────────────────────────────
describe('isHttpsRequest', () => {
  it('is true when x-forwarded-proto is https (TLS-terminating edge)', () => {
    expect(isHttpsRequest({ headers: { 'x-forwarded-proto': 'https' } })).toBe(true)
  })

  it('reads the first value of a comma-joined x-forwarded-proto', () => {
    expect(isHttpsRequest({ headers: { 'x-forwarded-proto': 'https, http' } })).toBe(true)
  })

  it('is true when the socket is encrypted (direct TLS)', () => {
    expect(isHttpsRequest({ headers: {}, socket: { encrypted: true } })).toBe(true)
  })

  it('is false for a plain http request', () => {
    expect(isHttpsRequest({ headers: {}, socket: { encrypted: false } })).toBe(false)
    expect(isHttpsRequest({ headers: { 'x-forwarded-proto': 'http' } })).toBe(false)
  })
})
