from __future__ import annotations

import json
import sys
from pathlib import Path
from types import SimpleNamespace

import pytest

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

import account_lock
import claudex
from account_lock import AccountLockedError, AccountLockStore, HumanOverrideError
from account_registry import AccountRegistry, AccountRegistryKind


def _codex_account(base_dir: Path, slug: str = "night") -> Path:
    auth_path = base_dir / "accounts" / slug / "CODEX_HOME" / "auth.json"
    auth_path.parent.mkdir(parents=True)
    auth_path.write_text(json.dumps({"tokens": {}}), encoding="utf-8")
    (base_dir / "accounts.json").write_text(
        json.dumps({"accounts": [{"slug": slug, "alias": "Owner"}]}),
        encoding="utf-8",
    )
    (base_dir / "default_slug").write_text(f"{slug}\n", encoding="utf-8")
    return auth_path


def test_direct_codex_selection_refuses_locked_account(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    _codex_account(tmp_path)
    AccountLockStore(tmp_path).set_locked("codex", "night", True)
    monkeypatch.setattr(claudex, "_codex_tray_dirs", lambda: (tmp_path, tmp_path / "legacy"))
    monkeypatch.setattr(claudex, "_require_codex_spend_headroom", lambda _slug: None)

    with pytest.raises(AccountLockedError):
        claudex.resolve_selected_account("night")


def test_direct_codex_legacy_copy_uses_canonical_lock_policy(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    current = tmp_path / "current"
    legacy = tmp_path / "legacy"
    auth_path = _codex_account(legacy)
    store = AccountLockStore(current)
    store.set_locked("codex", "night", True)
    monkeypatch.setattr(claudex, "_codex_tray_dirs", lambda: (current, legacy))
    monkeypatch.setattr(claudex, "_require_codex_spend_headroom", lambda _slug: None)

    with pytest.raises(AccountLockedError):
        claudex.resolve_selected_account("night")

    confirmations: list[tuple[str, str, str]] = []
    monkeypatch.setattr(
        account_lock,
        "confirm_human_action",
        lambda action, tool, slug: confirmations.append((action, tool, slug)),
    )
    selected = claudex.resolve_selected_account("night", True)

    assert selected.native_auth_path == auth_path
    assert selected.base_dir == current
    assert confirmations == [("use-once", "codex", "night")]
    assert store.is_locked("codex", "night") is True


def test_direct_codex_human_override_is_explicit_one_shot(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    auth_path = _codex_account(tmp_path)
    store = AccountLockStore(tmp_path)
    store.set_locked("codex", "night", True)
    monkeypatch.setattr(claudex, "_codex_tray_dirs", lambda: (tmp_path, tmp_path / "legacy"))
    monkeypatch.setattr(claudex, "_require_codex_spend_headroom", lambda _slug: None)
    confirmations: list[tuple[str, str, str]] = []
    monkeypatch.setattr(
        account_lock,
        "confirm_human_action",
        lambda action, tool, slug: confirmations.append((action, tool, slug)),
    )

    selected = claudex.resolve_selected_account("night", True)

    assert selected.slug == "night"
    assert selected.native_auth_path == auth_path
    assert selected.authorization is not None
    assert selected.authorization.locked_override is True
    assert confirmations == [("use-once", "codex", "night")]
    assert store.is_locked("codex", "night") is True

    with pytest.raises(HumanOverrideError, match="explicit --account"):
        claudex.resolve_selected_account(None, True)


def test_direct_human_override_rejects_implicit_or_fallback_selectors(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.setattr(claudex, "_codex_tray_dirs", lambda: (tmp_path, tmp_path / "legacy"))

    with pytest.raises(HumanOverrideError, match="explicit --account"):
        claudex.resolve_selected_account(None, True)
    with pytest.raises(HumanOverrideError, match="exactly one"):
        claudex.resolve_selected_account("night,agent", True)
    with pytest.raises(HumanOverrideError, match="explicit --account"):
        claudex.resolve_claude_account(None, True)
    with pytest.raises(HumanOverrideError, match="exactly one"):
        claudex.resolve_claude_account("night,agent", True)


def test_direct_claude_selection_enforces_same_lock(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    monkeypatch.setattr(claudex.Path, "home", lambda: tmp_path)
    base_dir = tmp_path / ".local/state/overdeck/systray/runtime"
    registry = AccountRegistry(base_dir=base_dir, kind=AccountRegistryKind.CLAUDE)
    account_home = registry.add_dir("night", "Owner")
    (account_home / ".credentials.json").write_text("{}", encoding="utf-8")
    registry.set_locked("night", True)
    monkeypatch.setattr(claudex.ClaudeAdapter, "sync_before_exec", lambda *_args: [])
    monkeypatch.setattr(
        claudex,
        "resolve_credentials",
        lambda _home: SimpleNamespace(path=account_home / ".credentials.json"),
    )

    with pytest.raises(AccountLockedError):
        claudex.resolve_claude_account("night")

    monkeypatch.setattr(account_lock, "confirm_human_action", lambda *_args: None)
    selected = claudex.resolve_claude_account("night", True)

    assert selected.slug == "night"
    assert selected.authorization is not None
    assert selected.authorization.locked_override is True
    assert registry.is_locked("night") is True


def test_parse_account_overrides_consumes_human_override_flag() -> None:
    accounts, forwarded = claudex.parse_account_overrides(
        ["--codex-account", "night", "--human-override-lock", "--model", "sol"]
    )

    assert accounts == claudex.AccountOverrides("night", None, True)
    assert forwarded == ["--model", "sol"]

    with pytest.raises(claudex.ClaudexError, match="does not take a value"):
        claudex.parse_account_overrides(["--human-override-lock=yes"])


def test_top_level_human_override_without_account_returns_policy_refusal(
    capsys: pytest.CaptureFixture[str],
) -> None:
    exit_code = claudex.main(["claudex", "--human-override-lock"])
    captured = capsys.readouterr()

    assert exit_code == 77
    assert "explicit --account" in captured.err
    assert json.loads(captured.out)["reason"] == "human-override-denied"


def test_override_flag_only_applies_to_explicitly_pinned_provider(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    selected = claudex.SelectedAccount(
        "night",
        tmp_path / "auth.json",
        tmp_path / "proxy",
    )
    claude = claudex.ClaudeAccount("work", tmp_path / "claude", {})
    codex_calls: list[tuple[object, ...]] = []
    claude_calls: list[tuple[object, ...]] = []

    def resolve_codex(*args: object) -> claudex.SelectedAccount:
        codex_calls.append(args)
        return selected

    def resolve_claude(*args: object) -> claudex.ClaudeAccount:
        claude_calls.append(args)
        return claude

    monkeypatch.setattr(claudex, "resolve_selected_account", resolve_codex)
    monkeypatch.setattr(claudex, "resolve_claude_account", resolve_claude)

    accounts = claudex.AccountOverrides("night", None, True)
    assert claudex._resolve_selected_for_overrides(accounts) is selected
    assert claudex._resolve_claude_for_overrides(accounts) is claude

    assert codex_calls == [("night", True)]
    assert claude_calls == [(None,)]
