"""GitHub **user** App (customer-owned repos) — JWT + installation token API.

Separate from ``GitHubAppClient`` (P2HS org scaffolding). Configure via
``GITHUB_USER_APP_ID`` + ``GITHUB_USER_APP_PRIVATE_KEY`` (+ optional *_FILE).
"""
from __future__ import annotations

import time
from pathlib import Path
from typing import Any, Optional

import httpx
import structlog
from jose import jwt as jose_jwt
from jose.exceptions import JWSError

from src.config import Settings, get_settings

logger = structlog.get_logger()
GITHUB_API = "https://api.github.com"


def _normalize_github_pem(raw: str) -> str:
    s = (raw or "").strip()
    if not s:
        return s
    if "\\n" in s:
        s = s.replace("\\n", "\n")
    if s.startswith("\ufeff"):
        s = s.lstrip("\ufeff")
    return s


def _resolve_private_key(settings: Settings) -> str:
    path = (getattr(settings, "github_user_app_private_key_file", None) or "").strip()
    if path:
        p = Path(path).expanduser()
        if not p.is_file():
            raise FileNotFoundError(f"GitHub user-app private key file not found: {p}")
        return _normalize_github_pem(p.read_text(encoding="utf-8"))
    return _normalize_github_pem(getattr(settings, "github_user_app_private_key", "") or "")


class GitHubUserAppClient:
    """Minimal client: app JWT, installation token, fetch installation + list repos."""

    def __init__(self, app_id: Optional[str] = None, private_key: Optional[str] = None):
        settings = get_settings()
        self._app_id = (app_id or getattr(settings, "github_user_app_id", "") or "").strip()
        self._private_key = private_key or _resolve_private_key(settings)
        self._token_cache: dict[int, tuple[str, float]] = {}

    def _jwt(self) -> str:
        now = int(time.time())
        payload = {"iat": now - 60, "exp": now + (10 * 60), "iss": self._app_id}
        if not self._private_key or "BEGIN" not in self._private_key:
            raise ValueError(
                "GitHub user-app private key missing. Set GITHUB_USER_APP_PRIVATE_KEY or "
                "GITHUB_USER_APP_PRIVATE_KEY_FILE."
            )
        try:
            return jose_jwt.encode(payload, self._private_key, algorithm="RS256")
        except JWSError as e:
            raise ValueError("GitHub user-app private key is not valid PEM (RS256).") from e

    async def _installation_token(self, installation_id: int) -> str:
        cached = self._token_cache.get(installation_id)
        if cached and time.time() < cached[1]:
            return cached[0]
        jwt = self._jwt()
        url = f"{GITHUB_API}/app/installations/{installation_id}/access_tokens"
        async with httpx.AsyncClient(timeout=60.0) as client:
            r = await client.post(
                url,
                headers={
                    "Authorization": f"Bearer {jwt}",
                    "Accept": "application/vnd.github+json",
                    "X-GitHub-Api-Version": "2022-11-28",
                },
            )
        if r.status_code != 201:
            logger.warning(
                "github_user_app_installation_token_failed",
                status=r.status_code,
                body=r.text[:500],
            )
            r.raise_for_status()
        data = r.json()
        token = data.get("token") or ""
        exp = data.get("expires_at")
        # Default 1h minus margin
        ttl = 50 * 60
        try:
            from datetime import datetime

            if exp:
                dt = datetime.fromisoformat(exp.replace("Z", "+00:00"))
                ttl = max(60, int((dt.timestamp() - time.time()) * 0.9))
        except Exception:
            pass
        self._token_cache[installation_id] = (token, time.time() + ttl)
        return token

    async def get_installation(self, installation_id: int) -> dict[str, Any]:
        jwt = self._jwt()
        url = f"{GITHUB_API}/app/installations/{installation_id}"
        async with httpx.AsyncClient(timeout=60.0) as client:
            r = await client.get(
                url,
                headers={
                    "Authorization": f"Bearer {jwt}",
                    "Accept": "application/vnd.github+json",
                    "X-GitHub-Api-Version": "2022-11-28",
                },
            )
        if r.status_code != 200:
            logger.warning(
                "github_user_app_get_installation_failed",
                installation_id=installation_id,
                status=r.status_code,
                body=r.text[:500],
            )
            r.raise_for_status()
        return r.json()

    async def list_installation_repositories(self, installation_id: int) -> list[dict[str, Any]]:
        """All repositories this installation can access (paginated)."""
        token = await self._installation_token(installation_id)
        out: list[dict[str, Any]] = []
        url: Optional[str] = f"{GITHUB_API}/installation/repositories"
        async with httpx.AsyncClient(timeout=120.0) as client:
            while url:
                r = await client.get(
                    url,
                    headers={
                        "Authorization": f"Bearer {token}",
                        "Accept": "application/vnd.github+json",
                        "X-GitHub-Api-Version": "2022-11-28",
                    },
                )
                r.raise_for_status()
                data = r.json()
                for repo in data.get("repositories") or []:
                    out.append(repo)
                # Link header pagination
                link = r.headers.get("Link", "")
                url = None
                for part in link.split(","):
                    if 'rel="next"' in part:
                        url = part.split(";")[0].strip().strip("<>")
                        break
        return out

    async def get_authenticated_clone_url(self, installation_id: int, repo_full_name: str) -> str:
        """Return clone URL with a user-app installation token for a visible customer repo."""
        token = await self._installation_token(installation_id)
        return f"https://x-access-token:{token}@github.com/{repo_full_name}.git"
