// Mastodon-Async-Refresh header parser tests. Locks down the wire grammar
// the Halflight server (and Mastodon ≥ 4.5) emits while a long-running
// background task is in flight, plus the defensive bounds the client applies
// to malformed/hostile retry values.
//
// The header is *only* emitted on `running` (Halflight audit M6 fix); the
// parser's job is to round-trip a well-formed value, ignore noise, and
// fail-closed (return null) when the header is absent or its `id` field is
// missing — `id` is the only structurally load-bearing field.

import { parseAsyncRefreshHeader } from 'src/lib/asyncRefresh'

function headers(value: string | null, name = 'Mastodon-Async-Refresh'): Headers {
  const h = new Headers()
  if (value !== null) h.set(name, value)
  return h
}

describe('parseAsyncRefreshHeader', () => {
  it('parses a well-formed header with all fields', () => {
    const spec = parseAsyncRefreshHeader(
      headers('id="context:42:refresh", retry=3, result_count=12')
    )
    expect(spec).toEqual({
      id: 'context:42:refresh',
      retrySeconds: 3,
      resultCount: 12,
    })
  })

  it('returns null when the header is absent', () => {
    expect(parseAsyncRefreshHeader(headers(null))).toBeNull()
  })

  it('returns null when `id` is missing (the only required field)', () => {
    // retry alone is meaningless — without an id, the client has nothing to
    // tag the in-flight refresh against.
    expect(parseAsyncRefreshHeader(headers('retry=3, result_count=5'))).toBeNull()
  })

  it('defaults `retry` to 3 when the attribute is absent', () => {
    const spec = parseAsyncRefreshHeader(headers('id="ctx:7:refresh"'))
    expect(spec).toEqual({ id: 'ctx:7:refresh', retrySeconds: 3, resultCount: null })
  })

  it('clamps a too-large `retry` to the 30 s ceiling', () => {
    // Defends against a hostile or buggy server pegging the UI affordance
    // to a multi-minute interval. Real servers default to 3.
    const spec = parseAsyncRefreshHeader(headers('id="x", retry=999'))
    expect(spec?.retrySeconds).toBe(30)
  })

  it('clamps a too-small `retry` to the 1 s floor', () => {
    // Defends against a server pegging us into a tight loop.
    const spec = parseAsyncRefreshHeader(headers('id="x", retry=0'))
    expect(spec?.retrySeconds).toBe(1)
  })

  it('ignores a non-numeric `retry` and falls back to the default', () => {
    const spec = parseAsyncRefreshHeader(headers('id="x", retry=abc'))
    expect(spec?.retrySeconds).toBe(3)
  })

  it('treats missing `result_count` as null (older Halflight builds)', () => {
    const spec = parseAsyncRefreshHeader(headers('id="x", retry=3'))
    expect(spec?.resultCount).toBeNull()
  })

  it('preserves a seeded zero `result_count` (distinct from absent)', () => {
    // A server emitting `result_count=0` is signalling "I have started the
    // walk, no rows yet"; this is meaningfully different from an older
    // build that omits the attribute entirely.
    const spec = parseAsyncRefreshHeader(headers('id="x", retry=3, result_count=0'))
    expect(spec?.resultCount).toBe(0)
  })

  it('is tolerant to attribute reordering', () => {
    const spec = parseAsyncRefreshHeader(
      headers('retry=3, id="context:42:refresh", result_count=5')
    )
    expect(spec).toEqual({
      id: 'context:42:refresh',
      retrySeconds: 3,
      resultCount: 5,
    })
  })

  it('accepts the lowercase header name (some HTTP libraries lowercase)', () => {
    const spec = parseAsyncRefreshHeader(
      headers('id="x", retry=3', 'mastodon-async-refresh')
    )
    expect(spec?.id).toBe('x')
  })

  it('strips surrounding double quotes from `id`', () => {
    const spec = parseAsyncRefreshHeader(headers('id="ctx:1:refresh", retry=3'))
    expect(spec?.id).toBe('ctx:1:refresh')
  })

  it('accepts an unquoted `id`', () => {
    const spec = parseAsyncRefreshHeader(headers('id=ctx:1:refresh, retry=3'))
    expect(spec?.id).toBe('ctx:1:refresh')
  })

  it('ignores unrecognised attributes without breaking', () => {
    // Forward-compat: a future Mastodon revision that adds a new attribute
    // (e.g. `progress=0.7`) must not poison a parse for clients on the
    // current grammar.
    const spec = parseAsyncRefreshHeader(
      headers('id="x", retry=3, result_count=2, progress=0.7')
    )
    expect(spec).toEqual({ id: 'x', retrySeconds: 3, resultCount: 2 })
  })
})
