"""Garmin Connect login + token management.

Two entry points share one token store (`./.secrets/` relative to cwd):

* CLI — `main()` prompts for email/password (+ MFA) and saves tokens.
* Web app — the helper functions below drive the same flow without `input()`,
  pausing between password submit and MFA code so a browser form can collect
  the code across reruns.

Tokens are valid ~1 year and are refreshed automatically by
`openrun.ingest.garmin_api` on subsequent runs.

Usage (CLI):
    python -m openrun.ingest.auth
    # or, via shim, from the project root:
    uv run auth.py
"""

from __future__ import annotations

import getpass
import os
from pathlib import Path
from typing import Any

import garth


def token_dir() -> Path:
    """Token store: `.secrets/` relative to the current working directory."""
    return Path.cwd() / ".secrets"


# Backwards-compatible private alias (other modules imported `_token_dir`).
def _token_dir() -> Path:
    return token_dir()


def has_tokens(td: Path | None = None) -> bool:
    """True if a non-empty token store exists (i.e. a prior login was saved)."""
    td = td or token_dir()
    return td.exists() and any(td.iterdir())


def resume(td: Path | None = None) -> bool:
    """Load saved tokens into the global garth client. Returns False if none/invalid."""
    td = td or token_dir()
    if not has_tokens(td):
        return False
    try:
        garth.resume(str(td))
        return True
    except Exception:  # noqa: BLE001 — corrupt/expired tokens → treat as logged out
        return False


def current_user(td: Path | None = None) -> str | None:
    """Username for the resumed session, or None if not logged in."""
    if not resume(td):
        return None
    try:
        return garth.client.username
    except Exception:  # noqa: BLE001
        return None


def begin_login(email: str, password: str, td: Path | None = None) -> tuple[str, Any]:
    """Start a login. Returns one of:

    * ``("ok", username)``       — logged in (no MFA); tokens already saved.
    * ``("needs_mfa", state)``   — pass ``state`` + the emailed/app code to
                                   ``complete_mfa`` to finish.

    Raises on bad credentials (garth surfaces an HTTP error).
    """
    result = garth.sso.login(email, password, client=garth.client, return_on_mfa=True)
    if isinstance(result, tuple) and result and result[0] == "needs_mfa":
        return "needs_mfa", result[1]
    # No MFA: result is (oauth1_token, oauth2_token).
    garth.client.oauth1_token, garth.client.oauth2_token = result
    return "ok", _save(td)


def complete_mfa(client_state: Any, code: str, td: Path | None = None) -> str:
    """Finish an MFA login with the user-supplied code. Returns the username."""
    oauth1, oauth2 = garth.sso.resume_login(client_state, code.strip())
    garth.client.oauth1_token, garth.client.oauth2_token = oauth1, oauth2
    return _save(td)


def _save(td: Path | None = None) -> str:
    td = td or token_dir()
    td.mkdir(parents=True, exist_ok=True)
    garth.save(str(td))
    return garth.client.username


def main() -> None:
    td = token_dir()
    td.mkdir(exist_ok=True)
    email = os.environ.get("GARMIN_EMAIL") or input("Garmin email: ").strip()
    password = os.environ.get("GARMIN_PASSWORD") or getpass.getpass("Garmin password: ")
    status, payload = begin_login(email, password, td)
    if status == "needs_mfa":
        code = input("MFA code: ").strip()
        user = complete_mfa(payload, code, td)
    else:
        user = payload
    print(f"Logged in as {user}. Tokens saved to {td}.")


if __name__ == "__main__":
    main()
