# Copyright (c) 2026 MergeGuide, Inc. All rights reserved.
# SPDX-License-Identifier: Proprietary
"""HTTP retry helper with exponential backoff for rate-limited APIs."""

from __future__ import annotations

import asyncio
import logging

import httpx

logger = logging.getLogger(__name__)

DEFAULT_MAX_RETRIES = 3
DEFAULT_BASE_DELAY = 2.0


async def request_with_retry(
    client: httpx.AsyncClient,
    method: str,
    url: str,
    *,
    max_retries: int = DEFAULT_MAX_RETRIES,
    base_delay: float = DEFAULT_BASE_DELAY,
    **kwargs,
) -> httpx.Response:
    """Make an HTTP request with retry on 429 responses.

    Parses the Retry-After header when available, otherwise uses
    exponential backoff (base_delay * 2^attempt).

    Args:
        client: httpx.AsyncClient to use.
        method: HTTP method (GET, POST, PUT, PATCH, DELETE).
        url: Request URL.
        max_retries: Maximum number of retries on 429.
        base_delay: Base delay in seconds for exponential backoff.
        **kwargs: Passed through to client.request().

    Returns:
        httpx.Response from the successful (or final) attempt.
    """
    last_response: httpx.Response | None = None

    for attempt in range(max_retries + 1):
        response = await client.request(method, url, **kwargs)
        last_response = response

        if response.status_code != 429 or attempt == max_retries:
            return response

        # Parse Retry-After header
        retry_after = response.headers.get("Retry-After")
        if retry_after:
            try:
                delay = float(retry_after)
            except ValueError:
                delay = base_delay * (2**attempt)
        else:
            delay = base_delay * (2**attempt)

        logger.warning(
            "Rate limited (429) on %s %s, retrying in %.1fs (attempt %d/%d)",
            method,
            url,
            delay,
            attempt + 1,
            max_retries,
        )
        await asyncio.sleep(delay)

    # Should not reach here, but satisfy type checker
    assert last_response is not None
    return last_response
