from __future__ import annotations

import json
import os
import subprocess
from pathlib import Path

import pytest

from account_registry import Account, AccountRef
from claude_credentials import pin_account_uuid, pinned_account_uuid
from claude_health_client import ClaudeHealthClient
from claude_identity import TokenIdentity
from claude_oauth import (
    ClaudeOAuthAuthError,
    ClaudeOAuthLoggedOutError,
    ClaudeOAuthTransportError,
    UsageReading,
)
from health_client import (
    AccountSnapshot,
    ExtraUsageSummary,
    HealthStatus,
    NamedLimit,
    SpendSummary,
)


def _account(home: Path) -> Account:
    return Account(
        AccountRef("claude", "avi"),
        "Avi",
        home,
        "avi@example.com",
        "max",
        "acct_123",
    )


@pytest.fixture(autouse=True)
def _default_owner_identity(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
    def fake_account_uuid_for(self: object, path: Path) -> str | None:
        try:
            path.relative_to(tmp_path)
        except ValueError:
            return None
        return "test-owner-uuid"

    monkeypatch.setattr(
        "claude_health_client.ClaudeTokenIdentity.account_uuid_for",
        fake_account_uuid_for,
    )
    monkeypatch.setattr(
        "claude_health_client.ClaudeTokenIdentity.identify_token",
        lambda self, _token: TokenIdentity("test-owner-uuid", None),
    )


def test_fetch_returns_ok_snapshot_from_auth_status_and_limit_cache(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()
    (account_home / "rate-limits-cache.json").write_text(
        json.dumps(
            {
                "five_hour": {
                    "pct": 120,
                    "resets_at": "2026-07-01T12:00:00Z",
                },
                "seven_day": {
                    "pct": -3,
                    "resets_at": 1_783_382_400,
                },
                "burst_window": {
                    "group": "burst",
                    "pct": 55,
                    "resets_at": "2026-07-02T00:00:00Z",
                    "active": True,
                    "ignored": "ok",
                },
                "inactive_window": {
                    "group": "other",
                    "pct": 88,
                    "resets_at": 1_783_400_000,
                    "active": False,
                },
                "extra_usage": {
                    "used": 3.5,
                    "limit": 10,
                    "unit": "hours",
                    "display": "3.5 / 10 hours",
                },
                "spend": {
                    "amount": 12.25,
                    "limit": 50,
                    "currency": "USD",
                    "display": "$12.25 / $50",
                },
                "unknown": None,
            }
        ),
        encoding="utf-8",
    )
    run_calls: list[dict[str, object]] = []

    def fake_run(
        cmd: list[str],
        *,
        check: bool,
        capture_output: bool,
        text: bool,
        env: dict[str, str],
        timeout: float,
    ) -> subprocess.CompletedProcess[str]:
        run_calls.append({"cmd": cmd, "env": env, "timeout": timeout})
        return subprocess.CompletedProcess(
            args=cmd,
            returncode=0,
            stdout=json.dumps(
                {
                    "loggedIn": True,
                    "authMethod": "claude.ai",
                    "apiProvider": "firstParty",
                }
            )
            + "\n",
            stderr="",
        )

    monkeypatch.setattr("claude_health_client.subprocess.run", fake_run)
    monkeypatch.setattr("claude_health_client.time.time", lambda: 1_700_000_000.0)

    snapshot = ClaudeHealthClient().fetch(_account(account_home))

    assert snapshot == AccountSnapshot(
        status=HealthStatus.OK,
        primary_used_pct=100,
        secondary_used_pct=0,
        primary_reset_at=1782907200.0,
        secondary_reset_at=1783382400.0,
        checked_at=1_700_000_000.0,
        detail=None,
        named_limits=(
            NamedLimit("five_hour", "primary", 100, 1782907200.0, True),
            NamedLimit("seven_day", "secondary", 0, 1783382400.0, True),
            NamedLimit("burst_window", "burst", 55, 1782950400.0, True),
        ),
        extra_usage=ExtraUsageSummary(
            used=3.5,
            limit=10.0,
            unit="hours",
            display="3.5 / 10 hours",
        ),
        spend=SpendSummary(
            amount=12.25,
            limit=50.0,
            currency="USD",
            display="$12.25 / $50",
        ),
    )
    assert len(run_calls) == 1
    assert run_calls[0]["cmd"] == ["claude", "auth", "status", "--json"]
    assert run_calls[0]["timeout"] == 10.0
    assert run_calls[0]["env"]["CLAUDE_CONFIG_DIR"] != str(account_home)


def test_fetch_does_not_leave_claude_cli_generated_dot_config_in_account_home(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()
    (account_home / ".credentials.json").write_text(
        '{"claudeAiOauth":{"accessToken":"token","refreshToken":"refresh"}}',
        encoding="utf-8",
    )
    (account_home / "claude.json").write_text(
        '{"oauthAccount":{"emailAddress":"avi@example.com"},"projects":{"/repo":{}}}',
        encoding="utf-8",
    )
    (account_home / "rate-limits-cache.json").write_text(
        '{"five_hour":{"percent":10}}',
        encoding="utf-8",
    )

    def fake_run(
        cmd: list[str],
        *,
        check: bool,
        capture_output: bool,
        text: bool,
        env: dict[str, str],
        timeout: float,
    ) -> subprocess.CompletedProcess[str]:
        config_dir = Path(env["CLAUDE_CONFIG_DIR"])
        assert config_dir != account_home
        (config_dir / ".claude.json").write_text(
            '{"firstStartTime":"now","migrationVersion":13}',
            encoding="utf-8",
        )
        return subprocess.CompletedProcess(
            args=cmd,
            returncode=0,
            stdout='{"loggedIn": true, "authMethod": "claude.ai"}\n',
            stderr="",
        )

    monkeypatch.setattr("claude_health_client.subprocess.run", fake_run)
    monkeypatch.setattr(
        "claude_health_client.ClaudeOAuthSession.get_usage_reading",
        lambda _self: UsageReading(
            payload={"five_hour": {"percent": 10}},
            access_token="fake-token",
        ),
    )
    monkeypatch.setattr("claude_health_client.time.time", lambda: 1_700_000_000.0)

    snapshot = ClaudeHealthClient().fetch(_account(account_home))

    assert snapshot.status == HealthStatus.OK
    assert not (account_home / ".claude.json").exists()
    assert json.loads((account_home / "claude.json").read_text(encoding="utf-8")) == {
        "oauthAccount": {"emailAddress": "avi@example.com"},
        "projects": {"/repo": {}},
    }


def test_fetch_reads_live_usage_and_persists_cache_when_auth_is_healthy(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()

    def fake_run(*_: object, **__: object) -> subprocess.CompletedProcess[str]:
        return subprocess.CompletedProcess(
            args=["claude", "auth", "status", "--json"],
            returncode=0,
            stdout='{"loggedIn": true, "authMethod": "claude.ai"}\n',
            stderr="",
        )

    class FakeSession:
        def __init__(
            self,
            credentials_path: Path,
            timeout_secs: float = 5.0,
            read_only: bool = False,
            allow_rotation: bool = False,
            rotation_backup_path=None,
        ) -> None:
            assert credentials_path == account_home / ".credentials.json"
            assert timeout_secs == 10.0

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

        def get_usage(self) -> dict[str, object]:
            return {
                "five_hour": {
                    "percent": 64,
                    "resets_at": "2026-07-01T12:00:00Z",
                },
                "seven_day": {
                    "percent": 31,
                    "resets_at": 1_783_382_400,
                },
                "extra_usage": {
                    "used": 2,
                    "limit": 5,
                    "unit": "hours",
                    "display": "2 / 5 hours",
                },
            }

    monkeypatch.setattr("claude_health_client.subprocess.run", fake_run)
    monkeypatch.setattr("claude_health_client.ClaudeOAuthSession", FakeSession)
    monkeypatch.setattr("claude_health_client.time.time", lambda: 1_700_000_000.0)

    snapshot = ClaudeHealthClient().fetch(_account(account_home))

    assert snapshot == AccountSnapshot(
        status=HealthStatus.OK,
        primary_used_pct=64,
        secondary_used_pct=31,
        primary_reset_at=1782907200.0,
        secondary_reset_at=1783382400.0,
        checked_at=1_700_000_000.0,
        named_limits=(
            NamedLimit("five_hour", "primary", 64, 1782907200.0, True),
            NamedLimit("seven_day", "secondary", 31, 1783382400.0, True),
        ),
        extra_usage=ExtraUsageSummary(
            used=2.0,
            limit=5.0,
            unit="hours",
            display="2 / 5 hours",
        ),
        spend=None,
    )
    assert json.loads((account_home / "rate-limits-cache.json").read_text(encoding="utf-8")) == {
        "five_hour": {
            "percent": 64,
            "resets_at": "2026-07-01T12:00:00Z",
        },
        "seven_day": {
            "percent": 31,
            "resets_at": 1_783_382_400,
        },
        "extra_usage": {
            "used": 2,
            "limit": 5,
            "unit": "hours",
            "display": "2 / 5 hours",
        },
    }


def test_fetch_reads_live_usage_on_every_successful_refresh(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()
    usage_values = iter((12, 27))
    usage_calls = 0

    monkeypatch.setattr(
        ClaudeHealthClient,
        "_auth_status",
        staticmethod(lambda _path, _creds, _timeout: HealthStatus.OK),
    )

    class HealthySession:
        def __init__(
            self,
            _credentials_path: Path,
            timeout_secs: float = 5.0,
            read_only: bool = False,
            allow_rotation: bool = False,
            rotation_backup_path=None,
        ) -> 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]:
            nonlocal usage_calls
            usage_calls += 1
            return {
                "five_hour": {"utilization": next(usage_values)},
                "seven_day": {"utilization": 34},
            }

    monkeypatch.setattr("claude_health_client.ClaudeOAuthSession", HealthySession)
    monkeypatch.setattr("claude_health_client.time.time", lambda: 1_700_000_000.0)
    client = ClaudeHealthClient()

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

    assert first.primary_used_pct == 12
    assert second.primary_used_pct == 27
    assert usage_calls == 2



def test_fetch_floors_a_zero_retry_after(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()
    clock = [1_000.0]
    usage_calls: list[Path] = []

    class RateLimitedSession:
        def __init__(
            self,
            credentials_path: Path,
            timeout_secs: float = 5.0,
            read_only: bool = False,
            allow_rotation: bool = False,
            rotation_backup_path=None,
        ) -> None:
            usage_calls.append(credentials_path)

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

        def get_usage(self) -> dict[str, object]:
            raise ClaudeOAuthTransportError(
                "Claude OAuth usage request failed with status 429",
                retry_after_seconds=0.0,
            )

    monkeypatch.setattr(
        ClaudeHealthClient,
        "_auth_status",
        staticmethod(lambda _home, _creds, _timeout: HealthStatus.OK),
    )
    monkeypatch.setattr("claude_health_client.ClaudeOAuthSession", RateLimitedSession)
    monkeypatch.setattr("claude_health_client.time.time", lambda: clock[0])
    client = ClaudeHealthClient()

    client.fetch(_account(account_home))
    clock[0] = 1_030.0
    client.fetch(_account(account_home))

    assert len(usage_calls) == 1


def test_fetch_honors_retry_after_without_marking_old_cache_fresh(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()
    cache_path = account_home / "rate-limits-cache.json"
    cache_path.write_text(
        '{"five_hour":{"percent":25},"seven_day":{"percent":50}}',
        encoding="utf-8",
    )
    os.utime(cache_path, (100.0, 100.0))
    clock = [1_000.0]
    auth_calls: list[Path] = []
    usage_calls: list[Path] = []

    def fake_auth_status(
        path: Path, _credentials_path: Path, _timeout_secs: float
    ) -> HealthStatus:
        auth_calls.append(path)
        return HealthStatus.OK

    class RateLimitedSession:
        def __init__(
            self,
            credentials_path: Path,
            timeout_secs: float = 5.0,
            read_only: bool = False,
            allow_rotation: bool = False,
            rotation_backup_path=None,
        ) -> None:
            usage_calls.append(credentials_path)

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

        def get_usage(self) -> dict[str, object]:
            if len(usage_calls) == 1:
                raise ClaudeOAuthTransportError(
                    "Claude OAuth usage request failed with status 429",
                    retry_after_seconds=442.0,
                )
            return {
                "five_hour": {"utilization": 30},
                "seven_day": {"utilization": 40},
            }

    monkeypatch.setattr(ClaudeHealthClient, "_auth_status", staticmethod(fake_auth_status))
    monkeypatch.setattr("claude_health_client.ClaudeOAuthSession", RateLimitedSession)
    monkeypatch.setattr("claude_health_client.time.time", lambda: clock[0])
    client = ClaudeHealthClient()

    first = client.fetch(_account(account_home))
    clock[0] = 1_200.0
    second = client.fetch(_account(account_home))
    clock[0] = 1_442.0
    third = client.fetch(_account(account_home))

    assert first.status == HealthStatus.UNKNOWN
    assert first.primary_used_pct == 25
    assert first.secondary_used_pct == 50
    assert first.checked_at == 100.0
    assert second == first
    assert third.status == HealthStatus.OK
    assert third.primary_used_pct == 30
    assert third.secondary_used_pct == 40
    assert auth_calls == [account_home, account_home, account_home]
    assert usage_calls == [
        account_home / ".credentials.json",
        account_home / ".credentials.json",
    ]


def _reject_usage(monkeypatch: pytest.MonkeyPatch, auth_status: HealthStatus) -> None:
    monkeypatch.setattr(
        ClaudeHealthClient,
        "_auth_status",
        staticmethod(lambda _path, _creds, _timeout: auth_status),
    )

    class RejectedSession:
        def __init__(
            self,
            _credentials_path: Path,
            timeout_secs: float = 5.0,
            read_only: bool = False,
            allow_rotation: bool = False,
            rotation_backup_path=None,
        ) -> 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]:
            raise ClaudeOAuthAuthError("Claude OAuth usage request failed with status 403")

    monkeypatch.setattr("claude_health_client.ClaudeOAuthSession", RejectedSession)
    monkeypatch.setattr("claude_health_client.time.time", lambda: 1_700_000_000.0)


def test_fetch_keeps_limits_when_usage_is_rejected_but_cli_auth_is_healthy(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()
    (account_home / "rate-limits-cache.json").write_text(
        '{"five_hour":{"percent":25},"seven_day":{"percent":50}}',
        encoding="utf-8",
    )
    _reject_usage(monkeypatch, HealthStatus.OK)
    client = ClaudeHealthClient()

    snapshot = client.fetch(_account(account_home))

    assert snapshot.status == HealthStatus.OK
    assert snapshot.primary_used_pct == 25
    assert snapshot.secondary_used_pct == 50
    assert client._read_backoff(account_home, None).retry_not_before > 1_700_000_000.0


def test_fetch_marks_server_rejected_token_broken_even_when_cli_auth_is_healthy(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()
    (account_home / "rate-limits-cache.json").write_text(
        '{"five_hour":{"percent":25},"seven_day":{"percent":50}}',
        encoding="utf-8",
    )
    monkeypatch.setattr(
        ClaudeHealthClient,
        "_auth_status",
        staticmethod(lambda _path, _creds, _timeout: HealthStatus.OK),
    )

    class LoggedOutSession:
        def __init__(
            self,
            _credentials_path: Path,
            timeout_secs: float = 5.0,
            read_only: bool = False,
            allow_rotation: bool = False,
            rotation_backup_path=None,
        ) -> 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]:
            raise ClaudeOAuthLoggedOutError(
                "Claude OAuth token refresh failed with status 401"
            )

    monkeypatch.setattr("claude_health_client.ClaudeOAuthSession", LoggedOutSession)
    monkeypatch.setattr("claude_health_client.time.time", lambda: 1_700_000_000.0)

    snapshot = ClaudeHealthClient().fetch(_account(account_home))

    assert snapshot.status == HealthStatus.BROKEN
    assert snapshot.detail == "authentication required"


def test_fetch_honors_retry_after_across_separate_client_instances(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()
    (account_home / "rate-limits-cache.json").write_text(
        '{"five_hour":{"percent":25},"seven_day":{"percent":50}}',
        encoding="utf-8",
    )
    usage_calls = 0

    class RateLimitedSession:
        def __init__(
            self,
            _credentials_path: Path,
            timeout_secs: float = 5.0,
            read_only: bool = False,
            allow_rotation: bool = False,
            rotation_backup_path=None,
        ) -> 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]:
            nonlocal usage_calls
            usage_calls += 1
            raise ClaudeOAuthTransportError(
                "Claude OAuth usage request failed with status 429",
                retry_after_seconds=442.0,
            )

    monkeypatch.setattr(
        ClaudeHealthClient,
        "_auth_status",
        staticmethod(lambda _path, _creds, _timeout: HealthStatus.OK),
    )
    monkeypatch.setattr("claude_health_client.ClaudeOAuthSession", RateLimitedSession)
    monkeypatch.setattr("claude_health_client.time.time", lambda: 1_000.0)

    ClaudeHealthClient().fetch(_account(account_home))
    ClaudeHealthClient().fetch(_account(account_home))

    assert usage_calls == 1


def test_fetch_marks_usage_auth_rejection_broken_when_cli_auth_is_inconclusive(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()
    (account_home / "rate-limits-cache.json").write_text(
        '{"five_hour":{"percent":25},"seven_day":{"percent":50}}',
        encoding="utf-8",
    )
    _reject_usage(monkeypatch, HealthStatus.UNKNOWN)

    snapshot = ClaudeHealthClient().fetch(_account(account_home))

    assert snapshot == AccountSnapshot(
        status=HealthStatus.BROKEN,
        primary_used_pct=None,
        secondary_used_pct=None,
        checked_at=1_700_000_000.0,
        detail="authentication required",
    )


def test_fetch_detects_broken_auth_during_usage_backoff(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()
    (account_home / "rate-limits-cache.json").write_text(
        '{"five_hour":{"percent":25},"seven_day":{"percent":50}}',
        encoding="utf-8",
    )
    auth_statuses = iter((HealthStatus.OK, HealthStatus.BROKEN))
    usage_calls = 0

    class RateLimitedSession:
        def __init__(
            self,
            _credentials_path: Path,
            timeout_secs: float = 5.0,
            read_only: bool = False,
            allow_rotation: bool = False,
            rotation_backup_path=None,
        ) -> 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]:
            nonlocal usage_calls
            usage_calls += 1
            raise ClaudeOAuthTransportError(
                "Claude OAuth usage request failed with status 429",
                retry_after_seconds=442.0,
            )

    monkeypatch.setattr(
        ClaudeHealthClient,
        "_auth_status",
        staticmethod(lambda _path, _creds, _timeout: next(auth_statuses)),
    )
    monkeypatch.setattr("claude_health_client.ClaudeOAuthSession", RateLimitedSession)
    monkeypatch.setattr("claude_health_client.time.time", lambda: 1_000.0)
    client = ClaudeHealthClient()

    client.fetch(_account(account_home))
    second = client.fetch(_account(account_home))

    assert second.status == HealthStatus.BROKEN
    assert second.primary_used_pct is None
    assert second.secondary_used_pct is None
    assert usage_calls == 1


def test_fetch_maps_claude_utilization_limits_to_usage_percentages(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()
    (account_home / "rate-limits-cache.json").write_text(
        json.dumps(
            {
                "five_hour": {
                    "utilization": 21.0,
                    "resets_at": "2026-07-03T19:50:00.094983+00:00",
                },
                "seven_day": {
                    "utilization": 44.0,
                    "resets_at": "2026-07-07T10:00:00.095013+00:00",
                },
            }
        ),
        encoding="utf-8",
    )

    def fake_run(*_: object, **__: object) -> subprocess.CompletedProcess[str]:
        return subprocess.CompletedProcess(
            args=["claude", "auth", "status", "--json"],
            returncode=0,
            stdout='{"loggedIn": true, "authMethod": "claude.ai"}\n',
            stderr="",
        )

    monkeypatch.setattr("claude_health_client.subprocess.run", fake_run)
    monkeypatch.setattr("claude_health_client.time.time", lambda: 1_700_000_000.0)

    snapshot = ClaudeHealthClient().fetch(_account(account_home))

    assert snapshot.primary_used_pct == 21
    assert snapshot.secondary_used_pct == 44
    assert snapshot.named_limits[:2] == (
        NamedLimit("five_hour", "primary", 21, 1783108200.094983, True),
        NamedLimit("seven_day", "secondary", 44, 1783418400.095013, True),
    )


def test_fetch_defaults_missing_claude_reset_times_to_window_lengths(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()
    (account_home / "rate-limits-cache.json").write_text(
        json.dumps(
            {
                "five_hour": {"utilization": 12.0},
                "seven_day": {"utilization": 34.0},
            }
        ),
        encoding="utf-8",
    )

    def fake_run(*_: object, **__: object) -> subprocess.CompletedProcess[str]:
        return subprocess.CompletedProcess(
            args=["claude", "auth", "status", "--json"],
            returncode=0,
            stdout='{"loggedIn": true, "authMethod": "claude.ai"}\n',
            stderr="",
        )

    monkeypatch.setattr("claude_health_client.subprocess.run", fake_run)
    monkeypatch.setattr("claude_health_client.time.time", lambda: 1_700_000_000.0)

    snapshot = ClaudeHealthClient().fetch(_account(account_home))

    assert snapshot.primary_reset_at == 1_700_018_000.0
    assert snapshot.secondary_reset_at == 1_700_604_800.0


def test_fetch_returns_broken_on_explicit_logged_out_status(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()

    def fake_run(*_: object, **__: object) -> subprocess.CompletedProcess[str]:
        return subprocess.CompletedProcess(
            args=["claude", "auth", "status", "--json"],
            returncode=1,
            stdout='{"loggedIn": false, "authMethod": "none"}\n',
            stderr="",
        )

    monkeypatch.setattr("claude_health_client.subprocess.run", fake_run)
    monkeypatch.setattr("claude_health_client.time.time", lambda: 1_700_000_000.0)

    snapshot = ClaudeHealthClient().fetch(_account(account_home))

    assert snapshot == AccountSnapshot(
        status=HealthStatus.BROKEN,
        primary_used_pct=None,
        secondary_used_pct=None,
        checked_at=1_700_000_000.0,
        detail="authentication required",
    )


def test_fetch_returns_broken_on_auth_rejection_text(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()

    def fake_run(*_: object, **__: object) -> subprocess.CompletedProcess[str]:
        return subprocess.CompletedProcess(
            args=["claude", "auth", "status", "--json"],
            returncode=1,
            stdout="not-json\n",
            stderr="Error: authentication required for Claude Code",
        )

    monkeypatch.setattr("claude_health_client.subprocess.run", fake_run)
    monkeypatch.setattr("claude_health_client.time.time", lambda: 1_700_000_000.0)

    snapshot = ClaudeHealthClient().fetch(_account(account_home))

    assert snapshot == AccountSnapshot(
        status=HealthStatus.BROKEN,
        primary_used_pct=None,
        secondary_used_pct=None,
        checked_at=1_700_000_000.0,
        detail="authentication required",
    )


def test_fetch_returns_unknown_on_auth_timeout(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()

    def fake_run(*_: object, **__: object) -> subprocess.CompletedProcess[str]:
        raise subprocess.TimeoutExpired(
            cmd=["claude", "auth", "status", "--json"],
            timeout=10.0,
        )

    monkeypatch.setattr("claude_health_client.subprocess.run", fake_run)
    monkeypatch.setattr("claude_health_client.time.time", lambda: 1_700_000_000.0)

    snapshot = ClaudeHealthClient().fetch(_account(account_home))

    assert snapshot == AccountSnapshot(
        status=HealthStatus.UNKNOWN,
        primary_used_pct=None,
        secondary_used_pct=None,
        checked_at=1_700_000_000.0,
    )


def test_fetch_uses_live_usage_when_cli_auth_status_is_inconclusive(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()

    def fake_run(*_: object, **__: object) -> subprocess.CompletedProcess[str]:
        raise subprocess.TimeoutExpired(
            cmd=["claude", "auth", "status", "--json"],
            timeout=10.0,
        )

    class HealthySession:
        def __init__(
            self,
            credentials_path: Path,
            timeout_secs: float = 5.0,
            read_only: bool = False,
            allow_rotation: bool = False,
            rotation_backup_path=None,
        ) -> None:
            assert credentials_path == account_home / ".credentials.json"

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

        def get_usage(self) -> dict[str, object]:
            return {
                "five_hour": {"utilization": 12},
                "seven_day": {"utilization": 34},
            }

    monkeypatch.setattr("claude_health_client.subprocess.run", fake_run)
    monkeypatch.setattr("claude_health_client.ClaudeOAuthSession", HealthySession)
    monkeypatch.setattr("claude_health_client.time.time", lambda: 1_700_000_000.0)

    snapshot = ClaudeHealthClient().fetch(_account(account_home))

    assert snapshot.status == HealthStatus.OK
    assert snapshot.primary_used_pct == 12
    assert snapshot.secondary_used_pct == 34


def test_fetch_treats_transient_auth_service_failures_as_unknown(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()

    def fake_run(*_: object, **__: object) -> subprocess.CompletedProcess[str]:
        return subprocess.CompletedProcess(
            args=["claude", "auth", "status", "--json"],
            returncode=1,
            stdout="not-json\n",
            stderr="Auth service temporarily unavailable",
        )

    monkeypatch.setattr("claude_health_client.subprocess.run", fake_run)
    monkeypatch.setattr("claude_health_client.time.time", lambda: 1_700_000_000.0)

    snapshot = ClaudeHealthClient().fetch(_account(account_home))

    assert snapshot == AccountSnapshot(
        status=HealthStatus.UNKNOWN,
        primary_used_pct=None,
        secondary_used_pct=None,
        checked_at=1_700_000_000.0,
    )


def test_fetch_returns_unknown_and_redacts_detail_when_usage_cache_is_unavailable(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()
    (account_home / "rate-limits-cache.json").write_text("{", encoding="utf-8")

    def fake_run(*_: object, **__: object) -> subprocess.CompletedProcess[str]:
        return subprocess.CompletedProcess(
            args=["claude", "auth", "status", "--json"],
            returncode=0,
            stdout='{"loggedIn": true, "authMethod": "claude.ai"}\n',
            stderr="sensitive backend detail",
        )

    monkeypatch.setattr("claude_health_client.subprocess.run", fake_run)
    monkeypatch.setattr("claude_health_client.time.time", lambda: 1_700_000_000.0)

    snapshot = ClaudeHealthClient().fetch(_account(account_home))

    assert snapshot == AccountSnapshot(
        status=HealthStatus.UNKNOWN,
        primary_used_pct=None,
        secondary_used_pct=None,
        checked_at=1_700_000_000.0,
        detail="limits unavailable",
        named_limits=(),
        extra_usage=None,
        spend=None,
    )


def test_fetch_reads_named_limits_lists_and_summary_aliases(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()
    (account_home / "rate-limits-cache.json").write_text(
        json.dumps(
            {
                "named_limits": [
                    {
                        "kind": "five_hour",
                        "group": "primary",
                        "percent": 42,
                        "resets_at": "2026-07-01T12:00:00Z",
                        "active": True,
                    },
                    {
                        "kind": "ignore_me",
                        "group": "burst",
                        "percent": 91,
                        "resets_at": 1_783_000_000,
                        "active": False,
                    },
                ],
                "extraUsage": {
                    "used": 1,
                    "limit": 2,
                    "unit": "hours",
                    "display": "1 / 2 hours",
                },
                "spendSummary": {
                    "amount": 7.0,
                    "limit": 20,
                    "currency": "USD",
                    "display": "$7 / $20",
                },
            }
        ),
        encoding="utf-8",
    )

    def fake_run(*_: object, **__: object) -> subprocess.CompletedProcess[str]:
        return subprocess.CompletedProcess(
            args=["claude", "auth", "status", "--json"],
            returncode=0,
            stdout='{"loggedIn": true, "authMethod": "claude.ai"}\n',
            stderr="",
        )

    monkeypatch.setattr("claude_health_client.subprocess.run", fake_run)
    monkeypatch.setattr("claude_health_client.time.time", lambda: 1_700_000_000.0)

    snapshot = ClaudeHealthClient().fetch(_account(account_home))

    assert snapshot == AccountSnapshot(
        status=HealthStatus.OK,
        primary_used_pct=42,
        secondary_used_pct=None,
        primary_reset_at=1782907200.0,
        secondary_reset_at=None,
        checked_at=1_700_000_000.0,
        named_limits=(
            NamedLimit("five_hour", "primary", 42, 1782907200.0, True),
        ),
        extra_usage=ExtraUsageSummary(
            used=1.0,
            limit=2.0,
            unit="hours",
            display="1 / 2 hours",
        ),
        spend=SpendSummary(
            amount=7.0,
            limit=20.0,
            currency="USD",
            display="$7 / $20",
        ),
    )


class _AttributionSession:
    token = "session-token"
    payload: dict[str, object] = {"five_hour": {"percent": 2, "resets_at": 4_100_000_000}}

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

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


def _attribution_home(tmp_path: Path, cached_percent: int | None = 40) -> Path:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()
    pin_account_uuid(account_home, "uuid-this-account")
    if cached_percent is not None:
        (account_home / "rate-limits-cache.json").write_text(
            json.dumps({"five_hour": {"percent": cached_percent}}),
            encoding="utf-8",
        )
    return account_home


def _stub_attribution_fetch(
    monkeypatch: pytest.MonkeyPatch,
    owner_uuid: str | None,
) -> None:
    monkeypatch.setattr(
        ClaudeHealthClient,
        "_auth_status",
        staticmethod(lambda _path, _creds, _timeout: HealthStatus.OK),
    )
    monkeypatch.setattr("claude_health_client.ClaudeOAuthSession", _AttributionSession)
    monkeypatch.setattr(
        "claude_health_client.ClaudeTokenIdentity.identify_token",
        lambda self, token: None if owner_uuid is None else TokenIdentity(owner_uuid, None),
    )


def test_fetch_discards_usage_reading_owned_by_another_account(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = _attribution_home(tmp_path)
    _stub_attribution_fetch(monkeypatch, "uuid-other-account")

    snapshot = ClaudeHealthClient().fetch(_account(account_home))

    assert snapshot.primary_used_pct == 40
    assert json.loads(
        (account_home / "rate-limits-cache.json").read_text(encoding="utf-8")
    ) == {"five_hour": {"percent": 40}}


def test_fetch_discards_usage_reading_with_unverifiable_owner(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = _attribution_home(tmp_path)
    _stub_attribution_fetch(monkeypatch, None)

    snapshot = ClaudeHealthClient().fetch(_account(account_home))

    assert snapshot.primary_used_pct == 40


def test_fetch_reports_unknown_when_misattributed_reading_has_no_cache(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = _attribution_home(tmp_path, cached_percent=None)
    _stub_attribution_fetch(monkeypatch, "uuid-other-account")

    snapshot = ClaudeHealthClient().fetch(_account(account_home))

    assert snapshot.status == HealthStatus.UNKNOWN
    assert snapshot.primary_used_pct is None
    assert snapshot.detail == "usage attribution unverified"


def test_fetch_accepts_usage_reading_owned_by_this_account(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = _attribution_home(tmp_path)
    _stub_attribution_fetch(monkeypatch, "uuid-this-account")

    snapshot = ClaudeHealthClient().fetch(_account(account_home))

    assert snapshot.status == HealthStatus.OK
    assert snapshot.primary_used_pct == 2
    assert json.loads(
        (account_home / "rate-limits-cache.json").read_text(encoding="utf-8")
    ) == _AttributionSession.payload


def test_fetch_pins_the_account_from_its_own_credentials_when_unpinned(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()
    monkeypatch.setattr(
        ClaudeHealthClient,
        "_auth_status",
        staticmethod(lambda _path, _creds, _timeout: HealthStatus.OK),
    )
    monkeypatch.setattr("claude_health_client.ClaudeOAuthSession", _AttributionSession)
    monkeypatch.setattr("claude_health_client.time.time", lambda: 1_700_000_000.0)

    assert pinned_account_uuid(account_home) is None
    ClaudeHealthClient().fetch(_account(account_home))

    assert pinned_account_uuid(account_home) == "test-owner-uuid"


def test_reading_owner_matches_returns_false_and_snapshot_unverified_without_a_pin(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()
    monkeypatch.setattr(
        ClaudeHealthClient,
        "_auth_status",
        staticmethod(lambda _path, _creds, _timeout: HealthStatus.OK),
    )
    monkeypatch.setattr("claude_health_client.ClaudeOAuthSession", _AttributionSession)
    monkeypatch.setattr("claude_health_client.time.time", lambda: 1_700_000_000.0)
    monkeypatch.setattr(
        "claude_health_client.ClaudeTokenIdentity.account_uuid_for",
        lambda self, _path: None,
    )

    reading = UsageReading(payload=_AttributionSession.payload, access_token="session-token")
    assert ClaudeHealthClient._reading_owner_matches(
        account_home,
        reading,
        None,
    ) is False

    snapshot = ClaudeHealthClient().fetch(_account(account_home))

    assert snapshot.status == HealthStatus.UNKNOWN
    assert snapshot.detail == "usage attribution unverified"
    assert snapshot.primary_used_pct is None
    assert pinned_account_uuid(account_home) is None


def _elapsed_window_home(tmp_path: Path) -> Path:
    account_home = tmp_path / "CLAUDE_HOME"
    account_home.mkdir()
    (account_home / "rate-limits-cache.json").write_text(
        json.dumps(
            {
                "five_hour": {"percent": 13, "resets_at": 1_700_010_000},
                "seven_day": {"percent": 97, "resets_at": 1_699_999_000},
            }
        ),
        encoding="utf-8",
    )
    return account_home


def test_cached_snapshot_drops_usage_from_an_elapsed_window(tmp_path: Path) -> None:
    account_home = _elapsed_window_home(tmp_path)
    os.utime(account_home / "rate-limits-cache.json", (1_700_000_000.0, 1_700_000_000.0))

    snapshot = ClaudeHealthClient._cached_snapshot(account_home, 1_700_000_000.0)

    assert snapshot.status == HealthStatus.OK
    assert snapshot.primary_used_pct == 13
    assert snapshot.secondary_used_pct == 0
    assert snapshot.secondary_reset_at == 1_699_999_000 + 604_800


def test_live_payload_drops_usage_from_an_elapsed_window() -> None:
    snapshot = ClaudeHealthClient._snapshot_from_payload(
        {"seven_day": {"percent": 97, "resets_at": 1_699_999_000}},
        HealthStatus.OK,
        1_700_000_000.0,
    )

    assert snapshot.secondary_used_pct == 0
