"""GitHub installation cache for user-scoped org lookups.

Caches the mapping of user → org logins (where peon-grukk is installed)
in the ``user_github_installations`` table to avoid per-request GitHub API calls.

Uses ``GET /user/orgs`` (works with any valid OAuth token) instead of
``GET /user/installations`` (which requires a GitHub App user-to-server token
and fails with 403 for regular OAuth tokens).
"""

from __future__ import annotations

import logging
from datetime import datetime, timezone

import httpx

logger = logging.getLogger(__name__)

# Cache TTL in seconds (15 minutes)
_CACHE_TTL_SECONDS = 900

_GITHUB_HEADERS = {
    "Accept": "application/vnd.github+json",
    "X-GitHub-Api-Version": "2022-11-28",
}


async def get_user_org_logins(
    db,
    user_id: int,
    encrypted_access_token: str,
) -> list[str]:
    """Return org/user logins the user belongs to that have our GitHub App installed.

    1. Check DB cache (15-minute TTL).
    2. On cache miss: decrypt token, call ``GET /user/orgs`` + ``GET /user``,
       intersect with known App installations, upsert cache.
    3. On GitHub API failure: return stale cached data (graceful degradation).

    Args:
        db: PgDatabase instance.
        user_id: Internal user ID.
        encrypted_access_token: Encrypted GitHub OAuth token from user_sessions.

    Returns:
        List of lowercased org/user logins where the App is installed.
    """
    # 1. Check cache
    cached = await _get_cached(db, user_id)
    if cached is not None:
        return cached

    # 2. Cache miss — fetch from GitHub
    try:
        from peon_mcp.auth.crypto import decrypt_token
        from peon_mcp.github_app import get_provider

        access_token = decrypt_token(encrypted_access_token)
        provider = get_provider()
        if provider is None:
            return await _get_stale_cached(db, user_id)

        # Get all logins the user is associated with (personal + orgs)
        user_logins = await _fetch_user_memberships(access_token)

        # Intersect with our App's known installations
        installed_orgs = set(provider.get_installations().keys())
        org_logins = [login for login in user_logins if login in installed_orgs]

        # Upsert cache
        await _upsert_cache(db, user_id, org_logins)

        return org_logins

    except Exception:
        logger.exception("Failed to fetch user org memberships from GitHub")
        # Graceful degradation: return stale cached data
        return await _get_stale_cached(db, user_id)


async def _get_cached(db, user_id: int) -> list[str] | None:
    """Return cached org logins if fresh (within TTL), else None."""
    rows = await db.execute_fetchall(
        "SELECT org_login, fetched_at FROM user_github_installations WHERE user_id = ?",
        (user_id,),
    )
    if not rows:
        return None

    # Check TTL on the most recent fetch
    now = datetime.now(timezone.utc)
    newest_fetch = max(r["fetched_at"] for r in rows)
    if newest_fetch.tzinfo is None:
        from datetime import timezone as tz
        newest_fetch = newest_fetch.replace(tzinfo=tz.utc)
    age_seconds = (now - newest_fetch).total_seconds()

    if age_seconds > _CACHE_TTL_SECONDS:
        return None  # Stale

    return [r["org_login"] for r in rows]


async def _get_stale_cached(db, user_id: int) -> list[str]:
    """Return cached org logins regardless of TTL (fallback on API failure)."""
    rows = await db.execute_fetchall(
        "SELECT org_login FROM user_github_installations WHERE user_id = ?",
        (user_id,),
    )
    return [r["org_login"] for r in rows]


async def _fetch_user_memberships(access_token: str) -> list[str]:
    """Fetch the user's personal login + org memberships from GitHub.

    Uses ``GET /user`` and ``GET /user/orgs`` which work with any valid
    OAuth token (unlike ``GET /user/installations`` which requires a
    GitHub App user-to-server token).
    """
    headers = {
        **_GITHUB_HEADERS,
        "Authorization": f"Bearer {access_token}",
    }

    logins: list[str] = []

    async with httpx.AsyncClient(timeout=10) as client:
        # Personal login
        user_resp = await client.get(
            "https://api.github.com/user",
            headers=headers,
        )
        user_resp.raise_for_status()
        personal_login = user_resp.json().get("login", "")
        if personal_login:
            logins.append(personal_login.lower())

        # Org memberships (public orgs without read:org scope, all with it)
        orgs_resp = await client.get(
            "https://api.github.com/user/orgs",
            headers=headers,
            params={"per_page": 100},
        )
        orgs_resp.raise_for_status()
        for org in orgs_resp.json():
            login = org.get("login", "")
            if login:
                logins.append(login.lower())

    return logins


async def _upsert_cache(
    db, user_id: int, org_logins: list[str]
) -> None:
    """Replace the user's cached installations."""
    # Delete old entries for this user
    await db.execute(
        "DELETE FROM user_github_installations WHERE user_id = ?",
        (user_id,),
    )
    # Insert new entries
    for org_login in org_logins:
        await db.execute(
            "INSERT INTO user_github_installations (user_id, org_login, installation_id, fetched_at)"
            " VALUES (?, ?, '', NOW())"
            " ON CONFLICT (user_id, org_login) DO UPDATE SET fetched_at = NOW()",
            (user_id, org_login),
        )
    await db.commit()
