"""Tests for `openrun.ingest.auth` — the web-friendly, MFA-resumable login flow.

We stub garth at the `sso.login` / `sso.resume_login` / `save` boundary so the
two-step (password → MFA code) state machine is testable without a real Garmin
account or network.
"""

from __future__ import annotations

from pathlib import Path

import openrun.ingest.auth as auth


class _FakeClient:
    username = "athlete"
    oauth1_token = None
    oauth2_token = None


def _stub_garth(monkeypatch):
    client = _FakeClient()
    monkeypatch.setattr(auth.garth, "client", client)
    monkeypatch.setattr(auth.garth, "save", lambda td: None)
    return client


# --- token store helpers ---------------------------------------------------

def test_has_tokens(tmp_path: Path) -> None:
    assert auth.has_tokens(tmp_path) is False
    (tmp_path / "oauth1_token.json").write_text("{}")
    assert auth.has_tokens(tmp_path) is True


def test_resume_no_tokens_is_false(tmp_path: Path, monkeypatch) -> None:
    called = {"n": 0}
    monkeypatch.setattr(auth.garth, "resume", lambda td: called.__setitem__("n", called["n"] + 1))
    assert auth.resume(tmp_path) is False
    assert called["n"] == 0  # short-circuits before touching garth


def test_resume_with_tokens(tmp_path: Path, monkeypatch) -> None:
    (tmp_path / "oauth1_token.json").write_text("{}")
    monkeypatch.setattr(auth.garth, "resume", lambda td: None)
    assert auth.resume(tmp_path) is True


def test_resume_swallows_bad_tokens(tmp_path: Path, monkeypatch) -> None:
    (tmp_path / "oauth1_token.json").write_text("garbage")

    def _boom(td):
        raise ValueError("corrupt")

    monkeypatch.setattr(auth.garth, "resume", _boom)
    assert auth.resume(tmp_path) is False


# --- login state machine ---------------------------------------------------

def test_begin_login_no_mfa(tmp_path: Path, monkeypatch) -> None:
    client = _stub_garth(monkeypatch)
    monkeypatch.setattr(auth.garth.sso, "login", lambda *a, **k: ("oauth1", "oauth2"))

    kind, payload = auth.begin_login("e@x.com", "pw", tmp_path)

    assert kind == "ok"
    assert payload == "athlete"
    assert client.oauth1_token == "oauth1" and client.oauth2_token == "oauth2"


def test_begin_login_needs_mfa(tmp_path: Path, monkeypatch) -> None:
    _stub_garth(monkeypatch)
    state = {"session": "opaque"}
    monkeypatch.setattr(auth.garth.sso, "login", lambda *a, **k: ("needs_mfa", state))

    kind, payload = auth.begin_login("e@x.com", "pw", tmp_path)

    assert kind == "needs_mfa"
    assert payload is state


def test_complete_mfa(tmp_path: Path, monkeypatch) -> None:
    client = _stub_garth(monkeypatch)
    seen = {}

    def _resume(state, code):
        seen["state"], seen["code"] = state, code
        return ("o1", "o2")

    monkeypatch.setattr(auth.garth.sso, "resume_login", _resume)

    user = auth.complete_mfa({"s": 1}, "  123456 ", tmp_path)

    assert user == "athlete"
    assert seen["code"] == "123456"  # trimmed
    assert client.oauth1_token == "o1" and client.oauth2_token == "o2"
