"""garminconnect-backed auth for openrun-sync.

garth's password login is Cloudflare-rate-limited (429 on sso.garmin.com) and
garth itself is deprecated. python-garminconnect 0.3.x authenticates with
Garmin's newer DI Bearer tokens (`.secrets/garmin_tokens.json`) and refreshes
them against connectapi.garmin.com — no SSO involved after the first login.

`activate()` logs in via garminconnect, then routes garth's API surface
(module-level `garth.connectapi`/`garth.download`, the `garth.http.client`
singleton used by `garth.stats`, and the `username` property) through the
garminconnect client, so all of `openrun.ingest.garmin_api` runs unchanged.

This is the slim version of ROADMAP P3 (`openrun-sync --backend=garminconnect`).
"""

from __future__ import annotations

from pathlib import Path
from typing import Any


def has_tokens(token_dir: Path) -> bool:
    return (token_dir / "garmin_tokens.json").exists()


def activate(token_dir: Path) -> str:
    """Authenticate from `token_dir` and patch garth. Returns the username."""
    import garth
    from garth.http import Client as GarthClient
    from garth.http import client as garth_singleton
    from garminconnect import Garmin

    gc = Garmin()
    gc.login(tokenstore=str(token_dir))  # loads DI tokens; auto-refreshes + re-saves

    def connectapi(path: str, **kwargs: Any) -> Any:
        return gc.connectapi(path, **kwargs)

    def download(path: str, **kwargs: Any) -> bytes:
        return gc.download(path, **kwargs)

    # Call-time lookups on the singleton (garth.stats.* resolve `http.client`).
    garth_singleton.connectapi = connectapi  # type: ignore[method-assign]
    garth_singleton.download = download  # type: ignore[method-assign]
    # Module-level names were bound to the original methods at import time.
    garth.connectapi = connectapi  # type: ignore[assignment]
    garth.download = download  # type: ignore[assignment]

    # `Client.username` is a read-only property; replace it on the class.
    # sync_resting_hr interpolates it into /usersummary-service/.../{username},
    # which requires the UUID displayName — userName ("o@...") gets a 403.
    profile = gc.connectapi("userprofile-service/socialProfile") or {}
    username = gc.display_name or profile.get("displayName") or profile.get("userName") or ""
    GarthClient.username = username  # type: ignore[assignment]
    return profile.get("userName") or username


def main() -> None:
    """One-time interactive login; saves DI tokens to ./.secrets/garmin_tokens.json.

    Uses garminconnect's cascading login strategies (mobile+cffi first, which
    impersonates a browser TLS fingerprint) — gets past the Cloudflare 429
    that blocks garth's plain SSO login.

        uv run python -m openrun.ingest.garminconnect_backend
    """
    import getpass
    import os

    from garminconnect import Garmin

    td = Path.cwd() / ".secrets"
    td.mkdir(exist_ok=True)

    # A parseable-but-dead tokenstore short-circuits the password fallback in
    # garminconnect's login(); park any existing file so we get a fresh login.
    stale = td / "garmin_tokens.json"
    if stale.exists():
        stale.rename(td / "garmin_tokens.json.bak")
        print(f"(moved old tokens to {stale.name}.bak)")

    email = os.environ.get("GARMIN_EMAIL") or input("Garmin email: ").strip()
    password = os.environ.get("GARMIN_PASSWORD") or getpass.getpass("Garmin password: ")

    gc = Garmin(email=email, password=password,
                prompt_mfa=lambda: input("MFA code: ").strip())
    gc.login(tokenstore=str(td))  # no tokens on disk now → password login, then dump
    print(f"Logged in as {gc.display_name or email}. Tokens saved to {td}.")
    print("Now run:  uv run openrun-sync --days 35")


if __name__ == "__main__":
    main()
