import json
from pathlib import Path
from typing import Any

import pytest

from home_agent.auth.google_auth import GoogleAuthManager


class DummyTokenStore:
    def __init__(self) -> None:
        self.saved: dict[str, dict[str, Any]] = {}

    def save(self, name: str, payload: dict[str, Any]) -> None:
        self.saved[name] = payload

    def load(self, name: str) -> dict[str, Any] | None:
        return self.saved.get(name)


class FakeCredentials:
    def to_json(self) -> str:
        return json.dumps({"token": "abc123"})


class FakeInstalledAppFlow:
    seen_config: dict[str, Any] | None = None

    @classmethod
    def from_client_config(
        cls,
        client_config: dict[str, Any],
        scopes: list[str],
    ) -> "FakeInstalledAppFlow":
        cls.seen_config = client_config
        return cls()

    def run_local_server(self, **kwargs: object) -> FakeCredentials:
        assert kwargs.get("port") == 0
        return FakeCredentials()


class FakeBrokenInstalledAppFlow:
    @classmethod
    def from_client_config(
        cls,
        client_config: dict[str, Any],
        scopes: list[str],
    ) -> "FakeBrokenInstalledAppFlow":
        return cls()

    def run_local_server(self, **kwargs: object) -> FakeCredentials:
        msg = "'NoneType' object has no attribute 'replace'"
        raise AttributeError(msg)


def test_init_desktop_auth_reads_client_secrets_file_when_env_missing(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    secrets_path = tmp_path / "client_secret_1234567890-example.apps.googleusercontent.com.json"
    secrets_path.write_text(
        json.dumps(
            {
                "installed": {
                    "client_id": "file-client-id",
                    "client_secret": "file-client-secret",
                    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
                    "token_uri": "https://oauth2.googleapis.com/token",
                    "redirect_uris": ["http://localhost"],
                }
            }
        ),
        encoding="utf-8",
    )
    monkeypatch.chdir(tmp_path)
    monkeypatch.delenv("GOOGLE_CLIENT_ID", raising=False)
    monkeypatch.delenv("GOOGLE_CLIENT_SECRET", raising=False)
    monkeypatch.setattr(
        "home_agent.auth.google_auth.InstalledAppFlow",
        FakeInstalledAppFlow,
    )

    store = DummyTokenStore()
    manager = GoogleAuthManager(store)
    creds = manager.init_desktop_auth(["https://www.googleapis.com/auth/gmail.readonly"])

    assert isinstance(creds, FakeCredentials)
    assert FakeInstalledAppFlow.seen_config == {
        "installed": {
            "client_id": "file-client-id",
            "client_secret": "file-client-secret",
            "auth_uri": "https://accounts.google.com/o/oauth2/auth",
            "token_uri": "https://oauth2.googleapis.com/token",
            "redirect_uris": ["http://localhost"],
        }
    }
    assert store.saved["google"] == {"token": "abc123"}


def test_init_desktop_auth_requires_env_or_client_secrets_file(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.chdir(tmp_path)
    monkeypatch.delenv("GOOGLE_CLIENT_ID", raising=False)
    monkeypatch.delenv("GOOGLE_CLIENT_SECRET", raising=False)

    manager = GoogleAuthManager(DummyTokenStore())
    with pytest.raises(RuntimeError, match="client_secret_\\*\\.json"):
        manager.init_desktop_auth(["scope"])


def test_init_desktop_auth_reports_failed_local_callback(
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    secrets_path = tmp_path / "client_secret_1234567890-example.apps.googleusercontent.com.json"
    secrets_path.write_text(
        json.dumps(
            {
                "installed": {
                    "client_id": "file-client-id",
                    "client_secret": "file-client-secret",
                    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
                    "token_uri": "https://oauth2.googleapis.com/token",
                    "redirect_uris": ["http://localhost"],
                }
            }
        ),
        encoding="utf-8",
    )
    monkeypatch.chdir(tmp_path)
    monkeypatch.setattr(
        "home_agent.auth.google_auth.InstalledAppFlow",
        FakeBrokenInstalledAppFlow,
    )

    manager = GoogleAuthManager(DummyTokenStore())
    with pytest.raises(RuntimeError, match="browser callback was not received"):
        manager.init_desktop_auth(["scope"])


def test_get_credentials_requires_reauth_when_stored_scopes_are_missing() -> None:
    store = DummyTokenStore()
    store.saved["google"] = {
        "client_id": "client-id",
        "client_secret": "client-secret",
        "refresh_token": "refresh-token",
        "token": "token",
        "token_uri": "https://oauth2.googleapis.com/token",
        "scopes": ["https://www.googleapis.com/auth/gmail.readonly"],
    }
    manager = GoogleAuthManager(store)
    with pytest.raises(RuntimeError, match="missing required scopes"):
        manager.get_credentials(
            [
                "https://www.googleapis.com/auth/gmail.readonly",
                "https://www.googleapis.com/auth/calendar.readonly",
            ]
        )
