from __future__ import annotations

import json
import sys
from pathlib import Path

import pytest

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from account_registry import Account, AccountRef
from claude_health_client import ClaudeHealthClient
from claude_oauth import ClaudeOAuthLoggedOutError, UsageReading
from health_client import HealthStatus


def _account(home: Path) -> Account:
    return Account(AccountRef("claude", "zync"), "Zync", home, "zync@example.com", "max", "acct_1")


def _write_credentials(home: Path, access_token: str, refresh_token: str) -> None:
    home.mkdir(parents=True, exist_ok=True)
    (home / ".credentials.json").write_text(
        json.dumps(
            {
                "claudeAiOauth": {
                    "accessToken": access_token,
                    "refreshToken": refresh_token,
                    "expiresAt": 4_100_000_000_000,
                }
            }
        ),
        encoding="utf-8",
    )


class _DeadGrantSession:
    calls = 0

    def __init__(self, credentials_path: Path, timeout_secs: float = 5.0, allow_rotation: bool = False, rotation_backup_path=None):
        pass

    def get_usage_reading(self) -> UsageReading:
        return UsageReading(payload=self.get_usage(), access_token="fake-token")

    def get_usage(self) -> dict[str, object]:
        type(self).calls += 1
        raise ClaudeOAuthLoggedOutError("dead grant")


@pytest.fixture
def dead_grant(monkeypatch: pytest.MonkeyPatch) -> type[_DeadGrantSession]:
    _DeadGrantSession.calls = 0
    monkeypatch.setattr(
        ClaudeHealthClient,
        "_auth_status",
        staticmethod(lambda _home, _creds, _timeout: HealthStatus.OK),
    )
    monkeypatch.setattr("claude_health_client.ClaudeOAuthSession", _DeadGrantSession)
    return _DeadGrantSession


def test_a_dead_grant_is_probed_once_and_then_served_from_the_backoff(
    dead_grant: type[_DeadGrantSession],
    tmp_path: Path,
) -> None:
    home = tmp_path / "CLAUDE_HOME"
    _write_credentials(home, "access", "refresh")
    client = ClaudeHealthClient()

    first = client.fetch(_account(home))
    second = client.fetch(_account(home))

    assert dead_grant.calls == 1
    assert first.status == HealthStatus.BROKEN
    assert second.status == HealthStatus.BROKEN
    assert second.detail == "authentication required"


def test_a_backoff_record_without_a_fingerprint_is_not_honoured(
    dead_grant: type[_DeadGrantSession],
    tmp_path: Path,
) -> None:
    home = tmp_path / "CLAUDE_HOME"
    _write_credentials(home, "access", "refresh")
    (home / "usage-backoff.json").write_text(
        json.dumps({"retry_not_before": 4_100_000_000.0}), encoding="utf-8"
    )
    client = ClaudeHealthClient()

    snapshot = client.fetch(_account(home))

    assert dead_grant.calls == 1
    assert snapshot.status == HealthStatus.BROKEN


def test_new_credentials_invalidate_the_backoff_immediately(
    dead_grant: type[_DeadGrantSession],
    tmp_path: Path,
) -> None:
    home = tmp_path / "CLAUDE_HOME"
    _write_credentials(home, "access", "refresh")
    client = ClaudeHealthClient()

    client.fetch(_account(home))
    _write_credentials(home, "access-after-login", "refresh-after-login")
    client.fetch(_account(home))

    assert dead_grant.calls == 2


class _TransportFailSession:
    def __init__(self, credentials_path: Path, timeout_secs: float = 5.0, allow_rotation: bool = False, rotation_backup_path=None):
        pass

    def get_usage_reading(self) -> UsageReading:
        from claude_oauth import ClaudeOAuthTransportError

        raise ClaudeOAuthTransportError("token refresh failed with status 503")


def test_a_failed_fetch_records_its_reason_in_the_backoff_file(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
    home = tmp_path / "CLAUDE_HOME"
    _write_credentials(home, "access", "refresh")
    monkeypatch.setattr(
        ClaudeHealthClient,
        "_auth_status",
        staticmethod(lambda _home, _creds, _timeout: HealthStatus.OK),
    )
    monkeypatch.setattr("claude_health_client.ClaudeOAuthSession", _TransportFailSession)

    ClaudeHealthClient().fetch(_account(home))

    backoff = json.loads((home / "usage-backoff.json").read_text(encoding="utf-8"))
    assert backoff["detail"] == "ClaudeOAuthTransportError: token refresh failed with status 503"
