from __future__ import annotations

from pathlib import Path
from types import SimpleNamespace

import pytest

from worai.core import google_oauth
from worai.errors import UsageError


class _Creds:
    def __init__(
        self,
        *,
        valid: bool = True,
        expired: bool = False,
        refresh_token: str | None = None,
        scopes: list[str] | None = None,
    ) -> None:
        self.valid = valid
        self.expired = expired
        self.refresh_token = refresh_token
        self.scopes = scopes or []
        self.refreshed = False

    def refresh(self, _request) -> None:
        self.refreshed = True
        self.valid = True

    def to_json(self) -> str:
        return '{"token":"x"}'


def test_load_google_credentials_uses_existing_valid_token(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    token = tmp_path / "token.json"
    token.write_text("{}", encoding="utf-8")
    creds = _Creds(valid=True, expired=False, scopes=["scope:a"])

    monkeypatch.setattr(google_oauth.os.path, "exists", lambda _p: True)
    monkeypatch.setattr(
        google_oauth.Credentials,
        "from_authorized_user_file",
        lambda _path, _scopes: creds,
    )
    monkeypatch.setattr(
        google_oauth.InstalledAppFlow,
        "from_client_secrets_file",
        lambda *_a, **_k: (_ for _ in ()).throw(AssertionError("flow should not run")),
    )

    result = google_oauth.load_google_credentials("client.json", str(token), 8080, ["scope:a"])
    assert result is creds
    assert token.read_text(encoding="utf-8") == '{"token":"x"}'


def test_load_google_credentials_refreshes_expired_token(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    token = tmp_path / "token.json"
    token.write_text("{}", encoding="utf-8")
    creds = _Creds(valid=True, expired=True, refresh_token="r", scopes=["scope:a"])

    monkeypatch.setattr(google_oauth.os.path, "exists", lambda _p: True)
    monkeypatch.setattr(google_oauth.Credentials, "from_authorized_user_file", lambda *_a, **_k: creds)

    result = google_oauth.load_google_credentials("client.json", str(token), 8080, ["scope:a"])
    assert result is creds
    assert creds.refreshed is True


def test_load_google_credentials_runs_oauth_flow_when_needed(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    token = tmp_path / "token.json"
    token.write_text("{}", encoding="utf-8")

    existing = _Creds(valid=True, expired=False, scopes=["scope:old"])
    fresh = _Creds(valid=True, expired=False, scopes=["scope:new"])
    calls: list[dict] = []

    monkeypatch.setattr(google_oauth.os.path, "exists", lambda _p: True)
    monkeypatch.setattr(google_oauth.Credentials, "from_authorized_user_file", lambda *_a, **_k: existing)

    def _flow_factory(_path, _scopes):
        def _run_local_server(**kwargs):
            calls.append(kwargs)
            return fresh

        return SimpleNamespace(run_local_server=_run_local_server)

    monkeypatch.setattr(google_oauth.InstalledAppFlow, "from_client_secrets_file", _flow_factory)

    result = google_oauth.load_google_credentials("client.json", str(token), 9090, ["scope:new"])
    assert result is fresh
    assert calls
    assert calls[0]["access_type"] == "offline"
    assert calls[0]["prompt"] == "consent"
    assert calls[0]["include_granted_scopes"] == "true"


def test_load_google_credentials_raises_without_client_secrets(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    token = tmp_path / "token.json"

    monkeypatch.setattr(google_oauth.os.path, "exists", lambda _p: False)

    with pytest.raises(UsageError, match="Google OAuth client secrets are required"):
        google_oauth.load_google_credentials(None, str(token), 8080, ["scope:a"])


def test_load_google_credentials_handles_invalid_stored_token_and_restores_env(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    token = tmp_path / "token.json"
    token.write_text("{}", encoding="utf-8")
    fresh = _Creds(valid=True, scopes=["scope:a"])

    monkeypatch.setenv("OAUTHLIB_RELAX_TOKEN_SCOPE", "0")
    monkeypatch.setattr(google_oauth.os.path, "exists", lambda _p: True)

    def _boom(*_a, **_k):
        raise ValueError("bad token")

    monkeypatch.setattr(google_oauth.Credentials, "from_authorized_user_file", _boom)
    monkeypatch.setattr(
        google_oauth.InstalledAppFlow,
        "from_client_secrets_file",
        lambda *_a, **_k: SimpleNamespace(run_local_server=lambda **_k2: fresh),
    )

    result = google_oauth.load_google_credentials("client.json", str(token), 8080, ["scope:a"])
    assert result is fresh
    assert google_oauth.os.environ["OAUTHLIB_RELAX_TOKEN_SCOPE"] == "0"
