/**
 * Bảo vệ build CSP/security headers khỏi regress: thêm/xóa directive đều phải
 * thông qua test này. Tham chiếu OWASP Secure Headers Project.
 */
import {
  buildContentSecurityPolicy,
  buildSecurityHeaders,
  type SecurityHeader,
} from '@/libs/security/headers'

const RECAPTCHA_HOSTS = [
  'https://www.google.com',
  'https://www.gstatic.com',
  'https://recaptcha.net',
] as const

const parseDirectives = (csp: string): Record<string, string[]> => {
  const map: Record<string, string[]> = {}
  for (const part of csp.split(';').map((p) => p.trim()).filter(Boolean)) {
    const [name, ...rest] = part.split(/\s+/)
    map[name] = rest
  }
  return map
}

const findHeader = (
  headers: SecurityHeader[],
  key: string,
): SecurityHeader | undefined =>
  headers.find((h) => h.key.toLowerCase() === key.toLowerCase())

describe('[libs/security] buildContentSecurityPolicy', () => {
  it('trả về string với các directive nối bằng `; `', () => {
    const csp = buildContentSecurityPolicy({ isProd: false })
    expect(typeof csp).toBe('string')
    expect(csp).toContain('; ')
  })

  it('định nghĩa default-src `self`', () => {
    const csp = buildContentSecurityPolicy({ isProd: true })
    expect(parseDirectives(csp)['default-src']).toEqual(["'self'"])
  })

  it('script-src luôn có `self` + `unsafe-inline` + reCAPTCHA hosts', () => {
    const csp = buildContentSecurityPolicy({ isProd: true })
    const tokens = parseDirectives(csp)['script-src']
    expect(tokens).toContain("'self'")
    expect(tokens).toContain("'unsafe-inline'")
    for (const host of RECAPTCHA_HOSTS) {
      expect(tokens).toContain(host)
    }
  })

  it('script-src KHÔNG chứa `unsafe-eval` ở môi trường production', () => {
    const csp = buildContentSecurityPolicy({ isProd: true })
    const tokens = parseDirectives(csp)['script-src']
    expect(tokens).not.toContain("'unsafe-eval'")
  })

  it('script-src CHỨA `unsafe-eval` ở môi trường non-production (dev/test)', () => {
    const csp = buildContentSecurityPolicy({ isProd: false })
    const tokens = parseDirectives(csp)['script-src']
    expect(tokens).toContain("'unsafe-eval'")
  })

  it('script-src không chứa empty token (joinSpace lọc falsy)', () => {
    const csp = buildContentSecurityPolicy({ isProd: true })
    const directive = csp
      .split(';')
      .map((p) => p.trim())
      .find((p) => p.startsWith('script-src '))
    expect(directive).toBeDefined()
    expect(directive).not.toMatch(/\s\s/)
    expect(directive).not.toMatch(/\s$/)
  })

  it('style-src cho phép `self` + `unsafe-inline` (Next.js inline styles)', () => {
    const csp = buildContentSecurityPolicy({ isProd: true })
    expect(parseDirectives(csp)['style-src']).toEqual([
      "'self'",
      "'unsafe-inline'",
    ])
  })

  it('img-src cho phép self/data/blob/https (consistent double-quote, không escape)', () => {
    const csp = buildContentSecurityPolicy({ isProd: true })
    expect(parseDirectives(csp)['img-src']).toEqual([
      "'self'",
      'data:',
      'blob:',
      'https:',
    ])
    expect(csp).not.toContain("\\'")
  })

  it('font-src cho phép self và data URI', () => {
    const csp = buildContentSecurityPolicy({ isProd: true })
    expect(parseDirectives(csp)['font-src']).toEqual(["'self'", 'data:'])
  })

  it('connect-src và frame-src whitelist toàn bộ reCAPTCHA hosts', () => {
    const csp = buildContentSecurityPolicy({ isProd: true })
    const directives = parseDirectives(csp)
    for (const host of RECAPTCHA_HOSTS) {
      expect(directives['connect-src']).toContain(host)
      expect(directives['frame-src']).toContain(host)
    }
    expect(directives['connect-src']).toContain("'self'")
    expect(directives['connect-src']).toContain('blob:')
    expect(directives['frame-src']).toContain("'self'")
  })

  it('frame-ancestors là `none` để chống clickjacking', () => {
    const csp = buildContentSecurityPolicy({ isProd: true })
    expect(parseDirectives(csp)['frame-ancestors']).toEqual(["'none'"])
  })

  it('object-src là `none` để chặn legacy plugin (Flash, Java applet)', () => {
    const csp = buildContentSecurityPolicy({ isProd: true })
    expect(parseDirectives(csp)['object-src']).toEqual(["'none'"])
  })

  it('base-uri và form-action giới hạn về `self`', () => {
    const csp = buildContentSecurityPolicy({ isProd: true })
    const d = parseDirectives(csp)
    expect(d['base-uri']).toEqual(["'self'"])
    expect(d['form-action']).toEqual(["'self'"])
  })

  it('worker-src cho phép self và blob:', () => {
    const csp = buildContentSecurityPolicy({ isProd: true })
    expect(parseDirectives(csp)['worker-src']).toEqual(["'self'", 'blob:'])
  })

  it('manifest-src giới hạn về `self`', () => {
    const csp = buildContentSecurityPolicy({ isProd: true })
    expect(parseDirectives(csp)['manifest-src']).toEqual(["'self'"])
  })

  it('có directive `upgrade-insecure-requests` (no value)', () => {
    const csp = buildContentSecurityPolicy({ isProd: true })
    expect(parseDirectives(csp)['upgrade-insecure-requests']).toEqual([])
  })

  it('không chứa block comment hoặc escape ký tự lạ', () => {
    const csp = buildContentSecurityPolicy({ isProd: true })
    expect(csp).not.toContain('/*')
    expect(csp).not.toContain("\\'")
    expect(csp).not.toContain('\n')
  })
})

describe('[libs/security] buildSecurityHeaders', () => {
  it('trả về array các header SecurityHeader', () => {
    const headers = buildSecurityHeaders({ isProd: false })
    expect(Array.isArray(headers)).toBe(true)
    for (const h of headers) {
      expect(typeof h.key).toBe('string')
      expect(typeof h.value).toBe('string')
      expect(h.key.length).toBeGreaterThan(0)
      expect(h.value.length).toBeGreaterThan(0)
    }
  })

  it('luôn có Content-Security-Policy được sinh từ buildContentSecurityPolicy', () => {
    const headers = buildSecurityHeaders({ isProd: false })
    const csp = findHeader(headers, 'Content-Security-Policy')
    expect(csp?.value).toBe(buildContentSecurityPolicy({ isProd: false }))
  })

  it('X-Frame-Options = DENY (defense-in-depth ngoài frame-ancestors)', () => {
    const headers = buildSecurityHeaders({ isProd: true })
    expect(findHeader(headers, 'X-Frame-Options')?.value).toBe('DENY')
  })

  it('X-Content-Type-Options = nosniff', () => {
    const headers = buildSecurityHeaders({ isProd: true })
    expect(findHeader(headers, 'X-Content-Type-Options')?.value).toBe('nosniff')
  })

  it('Referrer-Policy = strict-origin-when-cross-origin', () => {
    const headers = buildSecurityHeaders({ isProd: true })
    expect(findHeader(headers, 'Referrer-Policy')?.value).toBe(
      'strict-origin-when-cross-origin',
    )
  })

  it('X-DNS-Prefetch-Control = off', () => {
    const headers = buildSecurityHeaders({ isProd: true })
    expect(findHeader(headers, 'X-DNS-Prefetch-Control')?.value).toBe('off')
  })

  it('Cross-Origin-Opener-Policy = same-origin (mitigate Spectre/XS-Leaks)', () => {
    const headers = buildSecurityHeaders({ isProd: true })
    expect(findHeader(headers, 'Cross-Origin-Opener-Policy')?.value).toBe(
      'same-origin',
    )
  })

  it('Permissions-Policy disable các quyền nhạy cảm', () => {
    const headers = buildSecurityHeaders({ isProd: true })
    const value = findHeader(headers, 'Permissions-Policy')?.value ?? ''
    for (const feature of [
      'camera=()',
      'microphone=()',
      'geolocation=()',
      'payment=()',
      'usb=()',
      'magnetometer=()',
      'gyroscope=()',
      'accelerometer=()',
      'interest-cohort=()',
    ]) {
      expect(value).toContain(feature)
    }
  })

  it('Strict-Transport-Security chỉ xuất hiện ở production', () => {
    const prod = buildSecurityHeaders({ isProd: true })
    const dev = buildSecurityHeaders({ isProd: false })
    expect(findHeader(prod, 'Strict-Transport-Security')?.value).toBe(
      'max-age=63072000; includeSubDomains; preload',
    )
    expect(findHeader(dev, 'Strict-Transport-Security')).toBeUndefined()
  })

  it('không có header nào bị duplicate key', () => {
    const headers = buildSecurityHeaders({ isProd: true })
    const keys = headers.map((h) => h.key.toLowerCase())
    expect(new Set(keys).size).toBe(keys.length)
  })

  it('độ dài array dev = 7, prod = 8 (có thêm HSTS)', () => {
    expect(buildSecurityHeaders({ isProd: false })).toHaveLength(7)
    expect(buildSecurityHeaders({ isProd: true })).toHaveLength(8)
  })
})
