"""
Xero OAuth token refresh for Django multi-tenant credentials (stored on Client).

Keeps HTTP logic separate from app.services.xero_service so legacy .env / token_manager
paths stay unchanged for scripts and FastAPI-era code.
"""
import base64
import logging
import time
from typing import Optional, Tuple

import requests

logger = logging.getLogger(__name__)


def refresh_xero_access_token(
    client_id: str,
    client_secret: str,
    refresh_token: str,
    *,
    max_retries: int = 3,
    retry_delay: float = 1.0,
) -> Tuple[str, Optional[str]]:
    """
    Refresh access token using client credentials + refresh token.

    Returns:
        (access_token, new_refresh_token_or_none)

    Raises:
        ValueError on invalid_grant / invalid_client (same semantics as token_manager).
        requests.HTTPError / RuntimeError after retries exhausted.
    """
    if not client_id or not client_secret or not refresh_token:
        raise ValueError("client_id, client_secret, and refresh_token are required")

    token_url = "https://identity.xero.com/connect/token"
    auth_header = base64.b64encode(f"{client_id}:{client_secret}".encode("utf-8")).decode("utf-8")
    headers = {
        "Authorization": f"Basic {auth_header}",
        "Content-Type": "application/x-www-form-urlencoded",
    }

    last_exc = None
    for attempt in range(max_retries):
        try:
            response = requests.post(
                token_url,
                data={"grant_type": "refresh_token", "refresh_token": refresh_token},
                headers=headers,
                timeout=30,
            )

            if response.status_code == 200:
                data = response.json()
                access = data.get("access_token")
                new_refresh = data.get("refresh_token")
                if not access:
                    raise RuntimeError("Xero token response missing access_token")
                if new_refresh:
                    logger.info("Xero access token refreshed (tenant DB credentials); new refresh token received")
                else:
                    logger.warning("Xero refresh returned no new refresh_token (rotation may be disabled)")
                return access, new_refresh

            error_body = response.text
            logger.error(
                "Xero token refresh failed (attempt %s/%s): %s - %s",
                attempt + 1,
                max_retries,
                response.status_code,
                error_body,
            )

            try:
                err = response.json()
                err_type = err.get("error", "")
                if err_type == "invalid_grant":
                    raise ValueError(
                        "Invalid grant: refresh token expired or revoked. "
                        "Re-authorize this client in Xero and update the stored refresh token."
                    ) from None
                if err_type == "invalid_client":
                    raise ValueError("Invalid Xero client credentials (client id / secret).") from None
            except ValueError:
                raise
            except Exception:
                pass

            last_exc = response
            if attempt < max_retries - 1:
                delay = retry_delay * (2**attempt)
                logger.warning("Retrying Xero token refresh in %.1fs...", delay)
                time.sleep(delay)

        except requests.RequestException as e:
            last_exc = e
            logger.error("Network error during Xero token refresh: %s", e)
            if attempt < max_retries - 1:
                delay = retry_delay * (2**attempt)
                time.sleep(delay)

    if last_exc is not None and hasattr(last_exc, "raise_for_status"):
        last_exc.raise_for_status()
    raise RuntimeError("Failed to refresh Xero access token after retries")
