import { describe, expect, it } from 'vitest'
import { CacheHeaderError, cacheHeaders, etagFrom, notModified } from './http-cache-headers'

describe('cacheHeaders', () => {
  it('emits Cache-Control for the browser block', () => {
    expect(
      cacheHeaders({
        browser: {
          maxAge: 60,
          staleWhileRevalidate: 120,
          staleIfError: 300,
          mustRevalidate: true,
          immutable: true,
        },
      }),
    ).toEqual({
      'Cache-Control': 'max-age=60, stale-while-revalidate=120, stale-if-error=300, must-revalidate, immutable',
    })
  })

  it('defaults to Cloudflare-CDN-Cache-Control for CDN directives', () => {
    expect(cacheHeaders({ cdn: { maxAge: 600, staleWhileRevalidate: 3600 } })).toEqual({
      'Cache-Control': 'max-age=0, must-revalidate',
      'Cloudflare-CDN-Cache-Control': 'max-age=600, stale-while-revalidate=3600',
    })
  })

  it('emits CDN-Cache-Control for the generic profile', () => {
    expect(cacheHeaders({ profile: 'generic', cdn: { maxAge: 600 } })).toEqual({
      'Cache-Control': 'max-age=0, must-revalidate',
      'CDN-Cache-Control': 'max-age=600',
    })
  })

  it('emits Surrogate-Control for the fastly profile', () => {
    expect(cacheHeaders({ profile: 'fastly', cdn: { maxAge: 600 } })).toEqual({
      'Cache-Control': 'max-age=0, must-revalidate',
      'Surrogate-Control': 'max-age=600',
    })
  })

  it('joins tags into Cache-Tag', () => {
    expect(cacheHeaders({ tags: ['route:home', 'epoch:42'] })).toEqual({
      'Cache-Tag': 'route:home,epoch:42',
    })
  })

  it('returns an empty record when browser and cdn are absent', () => {
    expect(cacheHeaders({})).toEqual({})
  })

  it('rejects s-maxage with stale-while-revalidate in the CDN block', () => {
    expect(() => cacheHeaders({ cdn: { sMaxage: 60, staleWhileRevalidate: 120 } })).toThrow(CacheHeaderError)
    expect(() => cacheHeaders({ cdn: { sMaxage: 60, staleWhileRevalidate: 120 } })).toThrow(
      expect.objectContaining({ code: 'cdn_smaxage_with_stale_while_revalidate' }),
    )
  })

  it('rejects browser private with CDN directives', () => {
    expect(() => cacheHeaders({ browser: { private: true }, cdn: { maxAge: 60 } })).toThrow(
      expect.objectContaining({ code: 'private_browser_with_cdn' }),
    )
  })

  it('rejects browser no-store with CDN directives', () => {
    expect(() => cacheHeaders({ browser: { noStore: true }, cdn: { maxAge: 60 } })).toThrow(
      expect.objectContaining({ code: 'private_browser_with_cdn' }),
    )
  })

  it('rejects empty cache tags', () => {
    expect(() => cacheHeaders({ tags: ['route:home', ''] })).toThrow(
      expect.objectContaining({ code: 'invalid_cache_tag' }),
    )
  })

  it('rejects cache tags containing spaces', () => {
    expect(() => cacheHeaders({ tags: ['route:home page'] })).toThrow(
      expect.objectContaining({ code: 'invalid_cache_tag' }),
    )
  })

  it('rejects more than 1000 cache tags', () => {
    expect(() =>
      cacheHeaders({ tags: Array.from({ length: 1001 }, (_, index) => `tag:${index}`) }),
    ).toThrow(expect.objectContaining({ code: 'too_many_cache_tags' }))
  })

  it('rejects cache tags above 16 KB joined length', () => {
    expect(() => cacheHeaders({ tags: ['x'.repeat(16 * 1024 + 1)] })).toThrow(
      expect.objectContaining({ code: 'cache_tags_too_long' }),
    )
  })

  it('leaves Set-Cookie and Cookie handling to the host', () => {
    expect(cacheHeaders({ browser: { private: true } })).toEqual({
      'Cache-Control': 'private',
    })
  })

  it('supports no-cache plus stale-while-revalidate for always-revalidate CDN shape', () => {
    expect(cacheHeaders({ cdn: { noCache: true, staleWhileRevalidate: 60 } })).toEqual({
      'Cache-Control': 'max-age=0, must-revalidate',
      'Cloudflare-CDN-Cache-Control': 'no-cache, stale-while-revalidate=60',
    })
  })

  it('returns a plain host-agnostic header record', () => {
    const headers = cacheHeaders({ cdn: { maxAge: 60 } })
    expect(Object.getPrototypeOf(headers)).toBe(Object.prototype)
    expect(headers).not.toBeInstanceOf(Headers)
  })

  it('emits no Workers Cache billing or runtime metadata', () => {
    expect(Object.keys(cacheHeaders({ cdn: { maxAge: 60 } })).sort()).toEqual([
      'Cache-Control',
      'Cloudflare-CDN-Cache-Control',
    ])
  })
})

describe('etagFrom', () => {
  it('wraps a strong ETag around a content hash', () => {
    expect(etagFrom('abc123')).toBe('"abc123"')
  })

  it('supports weak ETags by opt-in', () => {
    expect(etagFrom('abc123', { weak: true })).toBe('W/"abc123"')
  })
})

describe('notModified', () => {
  it('returns false when If-None-Match is absent', () => {
    expect(notModified(null, '"abc123"')).toBe(false)
  })

  it('matches a strong ETag in a list', () => {
    expect(notModified('"old", "abc123"', '"abc123"')).toBe(true)
  })

  it('keeps commas inside quoted ETags while matching a list', () => {
    expect(notModified('"old,value", "abc,123"', '"abc,123"')).toBe(true)
  })

  it('handles weak validators', () => {
    expect(notModified('W/"abc123"', '"abc123"')).toBe(true)
    expect(notModified('"abc123"', 'W/"abc123"')).toBe(true)
  })

  it('matches the wildcard validator', () => {
    expect(notModified('*', '"abc123"')).toBe(true)
  })

  it('returns false when no validator matches', () => {
    expect(notModified('"old", W/"other"', '"abc123"')).toBe(false)
  })
})
