from __future__ import annotations

import json
import sys
from pathlib import Path

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

from claude_identity import ClaudeTokenIdentity
from claude_oauth import ClaudeOAuthRequest, ClaudeOAuthResponse

_PROFILE_BODY = json.dumps(
    {"account": {"uuid": "account-uuid", "email": "person@example.com"}}
)


def _write_credentials(path: Path, access_token: str) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(
        json.dumps(
            {"claudeAiOauth": {"accessToken": access_token, "refreshToken": "refresh"}}
        ),
        encoding="utf-8",
    )


class _Transport:
    def __init__(self, *responses: ClaudeOAuthResponse) -> None:
        self.requests: list[ClaudeOAuthRequest] = []
        self._responses = list(responses)

    def __call__(self, request: ClaudeOAuthRequest, _timeout: float) -> ClaudeOAuthResponse:
        self.requests.append(request)
        return self._responses[min(len(self.requests) - 1, len(self._responses) - 1)]


def test_a_token_is_verified_once_and_then_served_from_the_cache(tmp_path: Path) -> None:
    credentials = tmp_path / ".credentials.json"
    _write_credentials(credentials, "access")
    transport = _Transport(ClaudeOAuthResponse(200, _PROFILE_BODY))
    cache_path = tmp_path / "identity.json"

    first = ClaudeTokenIdentity(cache_path, transport).identify(credentials)
    second = ClaudeTokenIdentity(cache_path, transport).identify(credentials)

    assert first is not None and first.account_uuid == "account-uuid"
    assert first.email == "person@example.com"
    assert second == first
    assert len(transport.requests) == 1
    assert transport.requests[0].headers["Authorization"] == "Bearer access"


def test_a_new_token_is_verified_again(tmp_path: Path) -> None:
    credentials = tmp_path / ".credentials.json"
    _write_credentials(credentials, "access")
    transport = _Transport(ClaudeOAuthResponse(200, _PROFILE_BODY))
    identity = ClaudeTokenIdentity(tmp_path / "identity.json", transport)

    identity.identify(credentials)
    _write_credentials(credentials, "access-rotated")
    identity.identify(credentials)

    assert len(transport.requests) == 2


def test_a_throttled_profile_call_is_not_retried_before_retry_after(tmp_path: Path) -> None:
    credentials = tmp_path / ".credentials.json"
    _write_credentials(credentials, "access")
    transport = _Transport(
        ClaudeOAuthResponse(429, "", {"Retry-After": "600"}),
        ClaudeOAuthResponse(200, _PROFILE_BODY),
    )
    clock = [1_000.0]
    identity = ClaudeTokenIdentity(tmp_path / "identity.json", transport, now=lambda: clock[0])

    assert identity.identify(credentials) is None
    clock[0] = 1_500.0
    assert identity.identify(credentials) is None
    assert len(transport.requests) == 1

    clock[0] = 1_700.0
    verified = identity.identify(credentials)
    assert verified is not None and verified.account_uuid == "account-uuid"
    assert len(transport.requests) == 2


def test_a_zero_retry_after_still_holds_off_the_next_probe(tmp_path: Path) -> None:
    credentials = tmp_path / ".credentials.json"
    _write_credentials(credentials, "access")
    transport = _Transport(ClaudeOAuthResponse(429, "", {"Retry-After": "0"}))
    clock = [1_000.0]
    identity = ClaudeTokenIdentity(tmp_path / "identity.json", transport, now=lambda: clock[0])

    assert identity.identify(credentials) is None
    clock[0] = 1_030.0
    assert identity.identify(credentials) is None

    assert len(transport.requests) == 1


def test_a_rejected_token_has_no_identity(tmp_path: Path) -> None:
    credentials = tmp_path / ".credentials.json"
    _write_credentials(credentials, "access")
    transport = _Transport(ClaudeOAuthResponse(401, ""))
    identity = ClaudeTokenIdentity(tmp_path / "identity.json", transport)

    assert identity.identify(credentials) is None
    assert identity.account_uuid_for(credentials) is None
    assert len(transport.requests) == 1


def test_a_credentials_file_without_a_token_is_never_probed(tmp_path: Path) -> None:
    credentials = tmp_path / ".credentials.json"
    _write_credentials(credentials, "")
    transport = _Transport(ClaudeOAuthResponse(200, _PROFILE_BODY))

    assert ClaudeTokenIdentity(tmp_path / "identity.json", transport).identify(credentials) is None
    assert transport.requests == []


def test_identify_token_caches_by_token_value(tmp_path: Path) -> None:
    calls: list[str] = []

    def transport(request: object, timeout: float) -> object:
        calls.append("hit")
        from claude_oauth import ClaudeOAuthResponse

        return ClaudeOAuthResponse(
            status_code=200,
            body='{"account": {"uuid": "uuid-token-owner", "email": "owner@example.com"}}',
        )

    identity = ClaudeTokenIdentity(
        cache_path=tmp_path / "identity.json",
        transport=transport,
    )

    first = identity.identify_token("token-abc")
    second = identity.identify_token("token-abc")

    assert first is not None and first.account_uuid == "uuid-token-owner"
    assert second == first
    assert calls == ["hit"]
