#!/usr/bin/env python3
"""
Shared HTTP GET helper with exponential-backoff retry for rate limiting (429)
and transient server errors (5xx). Stdlib only.

Every search_*.py / verify_citation.py network call should go through this
instead of calling urllib.request.urlopen directly, so a single source being
temporarily rate-limited doesn't quietly degrade a whole batch of candidates
to "unverified" the way Semantic Scholar's 429s did before this existed.

Importable:
    from http_utils import fetch
"""
import time
import urllib.request
import urllib.error

RETRYABLE_CODES = {429, 500, 502, 503, 504}


def _wait_seconds(http_error, attempt, base=1.0, cap=20.0):
    retry_after = http_error.headers.get("Retry-After") if http_error.headers else None
    if retry_after:
        try:
            return min(float(retry_after), cap)
        except ValueError:
            pass
    return min(base * (2 ** attempt), cap)


def fetch(url, headers=None, timeout=20, max_retries=4):
    """GET url, retrying on 429/5xx with exponential backoff (honoring a
    Retry-After header when the server sends one). Returns the raw response
    bytes. Raises the underlying urllib error if retries are exhausted or the
    error isn't retryable (e.g. 404)."""
    req = urllib.request.Request(url, headers=headers or {})
    attempt = 0
    while True:
        try:
            with urllib.request.urlopen(req, timeout=timeout) as resp:
                return resp.read()
        except urllib.error.HTTPError as e:
            if e.code not in RETRYABLE_CODES or attempt >= max_retries:
                raise
            time.sleep(_wait_seconds(e, attempt))
            attempt += 1
