"""GitHub Copilot auth bridge that delegates login to LiteLLM."""

import asyncio
import inspect
import json
import os
from collections.abc import Callable, Iterator
from contextlib import contextmanager
from pathlib import Path
from threading import Lock
from typing import Any
from urllib.parse import urlparse

import litellm.llms.github_copilot.authenticator as litellm_copilot_auth
from litellm.llms.github_copilot.authenticator import Authenticator

from otto.log import get_logger

log = get_logger(__name__)

_DEFAULT_DOMAIN = "github.com"
_URL_PATCH_LOCK = Lock()
_TOKEN_DIR_ENV = "GITHUB_COPILOT_TOKEN_DIR"
_ACCESS_TOKEN_FILE_ENV = "GITHUB_COPILOT_ACCESS_TOKEN_FILE"
_API_KEY_FILE_ENV = "GITHUB_COPILOT_API_KEY_FILE"


async def _maybe_await(value: Any) -> Any:
    if inspect.isawaitable(value):
        return await value
    return value


def _token_paths() -> tuple[Path, Path]:
    token_dir = Path(os.getenv(_TOKEN_DIR_ENV, "~/.config/litellm/github_copilot")).expanduser()
    access_file = token_dir / os.getenv(_ACCESS_TOKEN_FILE_ENV, "access-token")
    api_key_file = token_dir / os.getenv(_API_KEY_FILE_ENV, "api-key.json")
    return access_file, api_key_file


def has_litellm_credentials() -> bool:
    """Return True when LiteLLM has a cached Copilot token."""
    access_file, api_key_file = _token_paths()

    try:
        if access_file.exists() and access_file.read_text(encoding="utf-8").strip():
            return True
    except OSError:
        pass

    try:
        payload = json.loads(api_key_file.read_text(encoding="utf-8"))
    except (json.JSONDecodeError, OSError):
        return False

    token = payload.get("token")
    return isinstance(token, str) and bool(token.strip())


def normalize_domain(raw: str) -> str | None:
    """Normalize domain/URL input into a hostname."""
    trimmed = str(raw or "").strip()
    if not trimmed:
        return None

    try:
        candidate = trimmed if "://" in trimmed else f"https://{trimmed}"
        parsed = urlparse(candidate)
    except Exception:
        return None

    host = (parsed.hostname or "").strip().lower()
    return host or None


def _enterprise_urls(domain: str) -> tuple[str, str, str]:
    return (
        f"https://{domain}/login/device/code",
        f"https://{domain}/login/oauth/access_token",
        f"https://api.{domain}/copilot_internal/v2/token",
    )


@contextmanager
def _patched_enterprise_urls(enterprise_domain: str | None) -> Iterator[None]:
    """Monkeypatch LiteLLM Authenticator URLs for GitHub Enterprise."""
    if not enterprise_domain:
        yield
        return

    with _URL_PATCH_LOCK:
        original = (
            litellm_copilot_auth.GITHUB_DEVICE_CODE_URL,
            litellm_copilot_auth.GITHUB_ACCESS_TOKEN_URL,
            litellm_copilot_auth.GITHUB_API_KEY_URL,
        )
        (
            litellm_copilot_auth.GITHUB_DEVICE_CODE_URL,
            litellm_copilot_auth.GITHUB_ACCESS_TOKEN_URL,
            litellm_copilot_auth.GITHUB_API_KEY_URL,
        ) = _enterprise_urls(enterprise_domain)
        try:
            yield
        finally:
            (
                litellm_copilot_auth.GITHUB_DEVICE_CODE_URL,
                litellm_copilot_auth.GITHUB_ACCESS_TOKEN_URL,
                litellm_copilot_auth.GITHUB_API_KEY_URL,
            ) = original


async def login(
    on_auth_url: Callable[[str, str], Any] | None = None,
    on_prompt: Callable[[dict[str, Any]], Any] | None = None,
    on_progress: Callable[[str], Any] | None = None,
    signal_cancelled: Callable[[], bool] | None = None,
) -> dict[str, Any]:
    """Run LiteLLM's GitHub Copilot device-code login."""
    _ = on_auth_url  # LiteLLM prints the auth URL + code directly.

    if signal_cancelled and signal_cancelled():
        raise RuntimeError("Login cancelled")

    raw_enterprise = ""
    if callable(on_prompt):
        raw_enterprise = str(
            await _maybe_await(
                on_prompt(
                    {
                        "message": "GitHub Enterprise URL/domain (blank for github.com)",
                        "allowEmpty": True,
                    }
                )
            )
            or ""
        ).strip()
    enterprise = normalize_domain(raw_enterprise) if raw_enterprise else None
    if raw_enterprise and not enterprise:
        raise RuntimeError("Invalid GitHub Enterprise URL/domain")

    if callable(on_progress):
        await _maybe_await(
            on_progress("LiteLLM will print the device login URL and verification code.")
        )

    def _run_login() -> str:
        with _patched_enterprise_urls(enterprise):
            authenticator = Authenticator()
            return authenticator.get_access_token()

    access_token = await asyncio.to_thread(_run_login)
    if not isinstance(access_token, str) or not access_token.strip():
        raise RuntimeError("LiteLLM did not return a GitHub access token.")

    if signal_cancelled and signal_cancelled():
        raise RuntimeError("Login cancelled")

    log.debug("Copilot OAuth login delegated to LiteLLM", enterprise=enterprise or _DEFAULT_DOMAIN)
    return {
        "provider": "copilot",
        "type": "oauth",
        "managedBy": "litellm",
        "enterpriseUrl": enterprise,
    }
