from __future__ import annotations

import json
import stat
import sys
from pathlib import Path

import pytest

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

from account_registry import AccountRegistry, AccountRegistryKind
from claude_credentials import pin_account_uuid

_LIVE_GRANT = json.dumps(
    {"claudeAiOauth": {"accessToken": "live", "refreshToken": "live-refresh"}}
)


def _registry(tmp_path: Path) -> AccountRegistry:
    legacy_claude_home = tmp_path / "claude"
    legacy_claude_home.mkdir()
    return AccountRegistry(
        base_dir=tmp_path / "tray",
        legacy_codex_home=tmp_path / "codex",
        kind=AccountRegistryKind.CLAUDE,
        legacy_claude_home=legacy_claude_home,
        active_claude_json=tmp_path / ".claude.json",
    )


def _add(registry: AccountRegistry, slug: str, account_uuid: str) -> Path:
    home = registry.add_dir(slug, slug.title())
    (home / ".credentials.json").write_text(
        json.dumps({"claudeAiOauth": {"accessToken": slug, "refreshToken": f"{slug}-refresh"}}),
        encoding="utf-8",
    )
    pin_account_uuid(home, account_uuid)
    return home


def _stub_identity(monkeypatch: pytest.MonkeyPatch, owner_uuid: str | None) -> None:
    class _Identity:
        def account_uuid_for(self, _credentials_path: Path) -> str | None:
            return owner_uuid

    monkeypatch.setattr("account_registry.ClaudeTokenIdentity", _Identity)


def test_activation_hands_a_vendor_written_grant_back_to_its_owner(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    registry = _registry(tmp_path)
    owner_home = _add(registry, "owner", "owner-uuid")
    _add(registry, "next", "next-uuid")
    live = registry.legacy_claude_home / ".credentials.json"
    live.write_text(_LIVE_GRANT, encoding="utf-8")
    _stub_identity(monkeypatch, "owner-uuid")

    registry.set_default(next(a for a in registry.list() if a.slug == "next"))

    assert (owner_home / ".credentials.json").read_text(encoding="utf-8") == _LIVE_GRANT
    assert stat.S_IMODE((owner_home / ".credentials.json").stat().st_mode) == 0o600
    assert live.is_symlink()


def test_a_poisoned_claude_json_without_a_pin_never_claims_the_live_grant(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    registry = _registry(tmp_path)
    poisoned_home = registry.add_dir("poisoned", "Poisoned")
    (poisoned_home / ".credentials.json").write_text(
        json.dumps({"claudeAiOauth": {"accessToken": "poisoned", "refreshToken": "poisoned-refresh"}}),
        encoding="utf-8",
    )
    (poisoned_home / ".claude.json").write_text(
        json.dumps({"oauthAccount": {"accountUuid": "owner-uuid"}}),
        encoding="utf-8",
    )
    _add(registry, "next", "next-uuid")
    live = registry.legacy_claude_home / ".credentials.json"
    live.write_text(_LIVE_GRANT, encoding="utf-8")
    _stub_identity(monkeypatch, "owner-uuid")

    registry.set_default(next(a for a in registry.list() if a.slug == "next"))

    assert not (poisoned_home / ".credentials.json").read_text(encoding="utf-8") == _LIVE_GRANT


def test_an_unattributable_live_grant_is_left_alone(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    registry = _registry(tmp_path)
    owner_home = _add(registry, "owner", "owner-uuid")
    _add(registry, "next", "next-uuid")
    live = registry.legacy_claude_home / ".credentials.json"
    live.write_text(_LIVE_GRANT, encoding="utf-8")
    _stub_identity(monkeypatch, None)

    registry.set_default(next(a for a in registry.list() if a.slug == "next"))

    assert json.loads((owner_home / ".credentials.json").read_text(encoding="utf-8")) == {
        "claudeAiOauth": {"accessToken": "owner", "refreshToken": "owner-refresh"}
    }


def test_a_symlinked_live_grant_is_never_probed(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    registry = _registry(tmp_path)
    owner_home = _add(registry, "owner", "owner-uuid")
    _add(registry, "next", "next-uuid")

    class _Identity:
        def account_uuid_for(self, _credentials_path: Path) -> str | None:
            raise AssertionError("a symlinked live grant already belongs to its account")

    monkeypatch.setattr("account_registry.ClaudeTokenIdentity", _Identity)

    registry.set_default(next(a for a in registry.list() if a.slug == "owner"))
    registry.set_default(next(a for a in registry.list() if a.slug == "next"))

    assert (owner_home / ".credentials.json").read_text(encoding="utf-8") == json.dumps(
        {"claudeAiOauth": {"accessToken": "owner", "refreshToken": "owner-refresh"}}
    )


def test_an_unclaimed_live_grant_is_rescued_not_destroyed(
    monkeypatch: pytest.MonkeyPatch,
    tmp_path: Path,
) -> None:
    registry = _registry(tmp_path)
    unpinned_home = registry.add_dir("unpinned", "Unpinned")
    (unpinned_home / ".credentials.json").write_text("{}", encoding="utf-8")
    _add(registry, "next", "next-uuid")
    live = registry.legacy_claude_home / ".credentials.json"
    live.write_text(_LIVE_GRANT, encoding="utf-8")
    _stub_identity(monkeypatch, "stranger-uuid")
    rescue_dir = tmp_path / "rescue"
    monkeypatch.setattr("account_registry.ROTATION_BACKUP_DIR", rescue_dir, raising=False)

    registry.set_default(next(a for a in registry.list() if a.slug == "next"))

    rescue = rescue_dir / "unclaimed-stranger-uuid.credentials.json"
    assert rescue.read_text(encoding="utf-8") == _LIVE_GRANT
    assert stat.S_IMODE(rescue.stat().st_mode) == 0o600
    assert (unpinned_home / ".credentials.json").read_text(encoding="utf-8") == "{}"
    assert live.is_symlink()
