"""GitHub App authentication — V2.2b

Generates JWTs signed with the App's private key (RS256) and exchanges them for
per-installation access tokens.  Used by delivery/github.py when
POSTURA_GITHUB_APP_ID and a private key are configured.

Public API:
    get_installation_token(installation_id) -> str
    is_app_configured() -> bool
"""
from __future__ import annotations

import logging
import time
from pathlib import Path

import jwt
import requests

from postura.config import settings

logger = logging.getLogger(__name__)

_GITHUB_API = "https://api.github.com"

# Cache: installation_id -> (token, expires_at_epoch)
_token_cache: dict[int, tuple[str, float]] = {}


def is_app_configured() -> bool:
    """Return True if GitHub App credentials are present in config."""
    return bool(settings.github_app_id and _load_private_key())


def _load_private_key() -> str:
    """Return PEM private key string from config or file, or '' if not set."""
    if settings.github_app_private_key:
        # Stored inline — replace literal \n sequences with real newlines
        return settings.github_app_private_key.replace("\\n", "\n")
    if settings.github_app_private_key_path:
        pem_path = Path(settings.github_app_private_key_path)
        if pem_path.exists():
            return pem_path.read_text()
        logger.warning("POSTURA_GITHUB_APP_PRIVATE_KEY_PATH does not exist: %s", pem_path)
    return ""


def _generate_jwt() -> str:
    """Generate a 10-minute JWT signed with the App's RS256 private key."""
    now = int(time.time())
    payload = {
        "iat": now - 60,        # issued 60s ago — clock skew tolerance
        "exp": now + (10 * 60), # 10-minute expiry (GitHub maximum)
        "iss": settings.github_app_id,
    }
    private_key = _load_private_key()
    if not private_key:
        raise RuntimeError("GitHub App private key not configured")
    return jwt.encode(payload, private_key, algorithm="RS256")


def get_installation_token(installation_id: int) -> str:
    """
    Return a valid installation access token for the given installation.

    Tokens are cached until 60 seconds before expiry to avoid round-trips.

    Raises RuntimeError if App credentials are not configured or the API call fails.
    """
    now = time.time()
    cached_token, expires_at = _token_cache.get(installation_id, ("", 0.0))
    if cached_token and now < expires_at - 60:
        return cached_token

    app_jwt = _generate_jwt()
    url = f"{_GITHUB_API}/app/installations/{installation_id}/access_tokens"
    headers = {
        "Authorization": f"Bearer {app_jwt}",
        "Accept": "application/vnd.github+json",
        "X-GitHub-Api-Version": "2022-11-28",
    }
    try:
        resp = requests.post(url, headers=headers, timeout=15)
        resp.raise_for_status()
    except requests.HTTPError as exc:
        raise RuntimeError(
            f"Failed to get installation token for {installation_id}: "
            f"{exc.response.status_code} {exc.response.text[:200]}"
        ) from exc
    except Exception as exc:
        raise RuntimeError(f"GitHub App token request failed: {exc}") from exc

    data = resp.json()
    token = data.get("token", "")
    # expires_at is ISO 8601 — parse it simply
    expires_iso = data.get("expires_at", "")
    try:
        from datetime import datetime, timezone
        expires_dt = datetime.fromisoformat(expires_iso.replace("Z", "+00:00"))
        expires_epoch = expires_dt.timestamp()
    except Exception:
        expires_epoch = now + 3600  # default 1 hour

    if not token:
        raise RuntimeError(f"Empty token in GitHub App response: {data}")

    _token_cache[installation_id] = (token, expires_epoch)
    logger.info("Fetched installation token for installation %s (expires %s)", installation_id, expires_iso)
    return token
