#!/bin/sh
""":"
SYSTRAY_CODEX_SWITCHER_SHIM_ACTIVE=1 exec "${PYTHON:-python3}" "$0" "$@"
":"""
from __future__ import annotations

import importlib.util
import json
import os
from pathlib import Path
import subprocess
import sys

import pytest

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

import systray_codex_switcher


def test_seed_routing_rules_derives_defaults_from_registry(tmp_path: Path) -> None:
    rules_path = tmp_path / "routing_rules.json"

    systray_codex_switcher.seed_routing_rules(
        rules_path,
        known_slugs={"avi", "rafa", "roy"},
        default_slug="roy",
    )

    assert json.loads(rules_path.read_text(encoding="utf-8")) == {
        "projects": {},
        "default": "roy",
        "fallback_chain": [],
        "fallback_trigger": "broken_or_quota_exhausted",
        "quota_exhausted_threshold_pct": 100,
        "account_caps": {},
    }


def test_seed_routing_rules_keeps_existing_file(tmp_path: Path) -> None:
    rules_path = tmp_path / "routing_rules.json"
    rules_path.write_text('{"default":"custom"}\n', encoding="utf-8")

    systray_codex_switcher.seed_routing_rules(
        rules_path, known_slugs={"avi", "rafa", "roy"}, default_slug="roy"
    )

    assert rules_path.read_text(encoding="utf-8") == '{"default":"custom"}\n'


def test_seed_routing_rules_falls_back_to_registered_slug(tmp_path: Path) -> None:
    rules_path = tmp_path / "routing_rules.json"

    systray_codex_switcher.seed_routing_rules(
        rules_path,
        known_slugs={"roy", "avi"},
        default_slug="ghost",
    )

    assert json.loads(rules_path.read_text(encoding="utf-8"))["default"] == "avi"


def test_seed_routing_rules_requires_registered_accounts(tmp_path: Path) -> None:
    rules_path = tmp_path / "routing_rules.json"

    with pytest.raises(systray_codex_switcher.RoutingRulesSetupRequiredError) as exc:
        systray_codex_switcher.seed_routing_rules(rules_path, known_slugs=set())

    assert "setup required" in str(exc.value)
    assert not rules_path.exists()


def test_acquire_single_instance_lock_rejects_second_holder(tmp_path: Path) -> None:
    lock_path = tmp_path / "tray.lock"

    first = systray_codex_switcher.acquire_single_instance_lock(lock_path)
    try:
        with pytest.raises(RuntimeError):
            systray_codex_switcher.acquire_single_instance_lock(lock_path)
    finally:
        first.close()


def test_acquire_single_instance_lock_reusable_after_release(tmp_path: Path) -> None:
    lock_path = tmp_path / "tray.lock"

    first = systray_codex_switcher.acquire_single_instance_lock(lock_path)
    first.close()

    second = systray_codex_switcher.acquire_single_instance_lock(lock_path)
    second.close()


def test_main_exits_when_already_running(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path, capsys: pytest.CaptureFixture[str]
) -> None:
    monkeypatch.setattr(systray_codex_switcher, "TRAY_DIR", tmp_path)
    held_handle = systray_codex_switcher.acquire_single_instance_lock(
        tmp_path / "codex-account-switcher.lock"
    )
    try:
        with pytest.raises(SystemExit) as exc:
            systray_codex_switcher.main()
        assert exc.value.code == 1
        assert "systray-ai is already running" in capsys.readouterr().err
    finally:
        held_handle.close()


def test_main_wires_registry_health_client_and_indicator(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
    events: list[object] = []
    monkeypatch.setenv("CODEX_HOME", "/home/user/.codex-tray/accounts/rafa/CODEX_HOME")

    def fake_seed(path: Path, known_slugs=None, default_slug=None) -> None:
        events.append(("seed", path, known_slugs, default_slug))

    class FakeRegistry:
        def __init__(self, name: str) -> None:
            self.name = name

        def migrate_legacy(self) -> None:
            events.append(("migrate_legacy", self.name))

        def list(self):
            events.append("list")
            return [
                type("Account", (), {"slug": "avi"})(),
                type("Account", (), {"slug": "rafa"})(),
                type("Account", (), {"slug": "roy"})(),
            ]

        def default_slug(self) -> str:
            return "roy"

    class FakeGrokAuthOperation:
        def import_from_legacy_home(self, *, slug: str, alias: str) -> None:
            events.append(("grok_import_from_legacy_home", slug, alias))

    sentinel_registry = FakeRegistry("codex")
    sentinel_health_client = object()
    sentinel_device_auth_flow = object()
    sentinel_device_auth_operation = object()
    sentinel_codex_lifecycle = object()
    sentinel_codex_health = object()
    sentinel_claude_lifecycle = object()
    sentinel_claude_health = object()
    sentinel_grok_lifecycle = FakeGrokAuthOperation()
    sentinel_grok_health = object()
    sentinel_provider_map = object()

    class FakeHealthClient:
        def __init__(self) -> None:
            events.append("health_client_init")

    class FakeIndicator:
        def __init__(
            self,
            registry,
            health_client,
            claude_registry=None,
            grok_registry=None,
            provider_services=None,
            device_auth_flow=None,
            device_auth_operation=None,
        ) -> None:
            events.append(
                (
                    "indicator_init",
                    registry,
                    health_client,
                    claude_registry,
                    grok_registry,
                    provider_services,
                    device_auth_flow,
                    device_auth_operation,
                )
            )

        def build(self) -> None:
            events.append("build")

        def run(self) -> None:
            events.append("run")

    monkeypatch.setattr(systray_codex_switcher, "TRAY_DIR", tmp_path)
    monkeypatch.setattr(systray_codex_switcher, "seed_routing_rules", fake_seed)

    sentinel_claude_registry = FakeRegistry("claude")
    sentinel_grok_registry = FakeRegistry("grok")

    def registry_factory(**kwargs):
        events.append(("registry_factory", kwargs))
        if kwargs.get("kind") == systray_codex_switcher.AccountRegistryKind.CLAUDE:
            return sentinel_claude_registry
        if kwargs.get("kind") == systray_codex_switcher.AccountRegistryKind.GROK:
            return sentinel_grok_registry
        return sentinel_registry

    def health_factory():
        events.append("health_factory")
        return sentinel_health_client

    monkeypatch.setattr(systray_codex_switcher, "AccountRegistry", registry_factory)
    monkeypatch.setattr(systray_codex_switcher, "AccountHealthClient", health_factory)
    monkeypatch.setattr(
        systray_codex_switcher,
        "DeviceAuthFlow",
        lambda: events.append("device_auth_flow_factory") or sentinel_device_auth_flow,
    )
    monkeypatch.setattr(
        systray_codex_switcher,
        "DeviceAuthOperation",
        lambda registry, flow: events.append(("device_auth_operation", registry, flow))
        or sentinel_device_auth_operation,
    )
    monkeypatch.setattr(
        systray_codex_switcher,
        "CodexLifecycleAdapter",
        lambda operation: events.append(("codex_lifecycle_adapter", operation))
        or sentinel_codex_lifecycle,
    )
    monkeypatch.setattr(
        systray_codex_switcher,
        "CodexHealthAdapter",
        lambda client: events.append(("codex_health_adapter", client)) or sentinel_codex_health,
    )
    monkeypatch.setattr(
        systray_codex_switcher,
        "ClaudeAuthOperation",
        lambda registry: events.append(("claude_auth_operation", registry)) or sentinel_claude_lifecycle,
    )
    monkeypatch.setattr(
        systray_codex_switcher,
        "ClaudeHealthClient",
        lambda: events.append("claude_health_client_factory") or sentinel_claude_health,
    )

    def grok_auth_operation_factory(registry):
        events.append(("grok_auth_operation", registry))
        return sentinel_grok_lifecycle

    monkeypatch.setattr(
        systray_codex_switcher, "GrokAuthOperation", grok_auth_operation_factory
    )
    monkeypatch.setattr(
        systray_codex_switcher,
        "GrokHealthClient",
        lambda: events.append("grok_health_client_factory") or sentinel_grok_health,
    )

    class FakeProviderServices:
        def __init__(self, *, tool, registry, lifecycle, health) -> None:
            events.append(("provider_services", tool, registry, lifecycle, health))

    monkeypatch.setattr(systray_codex_switcher, "ProviderServices", FakeProviderServices)
    monkeypatch.setattr(
        systray_codex_switcher,
        "ProviderServiceMap",
        lambda providers: events.append(("provider_service_map", tuple(providers)))
        or sentinel_provider_map,
    )
    monkeypatch.setattr(systray_codex_switcher, "Indicator", FakeIndicator)

    systray_codex_switcher.main()

    assert "CODEX_HOME" not in systray_codex_switcher.os.environ
    assert events[:10] == [
        (
            "registry_factory",
            {"kind": systray_codex_switcher.AccountRegistryKind.CODEX},
        ),
        ("migrate_legacy", "codex"),
        "list",
        ("seed", tmp_path / "routing_rules.json", {"avi", "rafa", "roy"}, "roy"),
        (
            "registry_factory",
            {"kind": systray_codex_switcher.AccountRegistryKind.CLAUDE},
        ),
        ("migrate_legacy", "claude"),
        "list",
        ("seed", tmp_path / "claude_routing_rules.json", {"avi", "rafa", "roy"}, "roy"),
        (
            "registry_factory",
            {"kind": systray_codex_switcher.AccountRegistryKind.GROK},
        ),
        ("grok_auth_operation", sentinel_grok_registry),
    ]
    assert "health_factory" in events
    assert "device_auth_flow_factory" in events
    assert ("device_auth_operation", sentinel_registry, sentinel_device_auth_flow) in events
    assert ("codex_lifecycle_adapter", sentinel_device_auth_operation) in events
    assert ("codex_health_adapter", sentinel_health_client) in events
    assert ("claude_auth_operation", sentinel_claude_registry) in events
    assert "claude_health_client_factory" in events
    assert ("grok_auth_operation", sentinel_grok_registry) in events
    assert "grok_health_client_factory" in events
    assert (
        "provider_services",
        systray_codex_switcher.AccountRegistryKind.CODEX,
        sentinel_registry,
        sentinel_codex_lifecycle,
        sentinel_codex_health,
    ) in events
    assert (
        "provider_services",
        systray_codex_switcher.AccountRegistryKind.CLAUDE,
        sentinel_claude_registry,
        sentinel_claude_lifecycle,
        sentinel_claude_health,
    ) in events
    assert (
        "provider_services",
        systray_codex_switcher.AccountRegistryKind.GROK,
        sentinel_grok_registry,
        sentinel_grok_lifecycle,
        sentinel_grok_health,
    ) in events
    provider_map_event = next(event for event in events if event[0] == "provider_service_map")
    assert len(provider_map_event[1]) == 3
    assert (
        "indicator_init",
        sentinel_registry,
        sentinel_health_client,
        sentinel_claude_registry,
        sentinel_grok_registry,
        sentinel_provider_map,
        sentinel_device_auth_flow,
        sentinel_device_auth_operation,
    ) in events
    assert events[-2:] == ["build", "run"]


def test_main_propagates_startup_exceptions(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
    monkeypatch.setattr(systray_codex_switcher, "TRAY_DIR", tmp_path)

    def exploding_seed(
        _path: Path, known_slugs: set[str] | None = None, default_slug: str | None = None
    ) -> None:
        raise RuntimeError("startup failed")

    monkeypatch.setattr(systray_codex_switcher, "seed_routing_rules", exploding_seed)

    with pytest.raises(RuntimeError, match="startup failed"):
        systray_codex_switcher.main()


def test_atomic_write_replaces_existing_target_file(tmp_path: Path) -> None:
    destination = tmp_path / "routing_rules.json"
    systray_codex_switcher._atomic_write(destination, "first\n")
    systray_codex_switcher._atomic_write(destination, "second\n")

    assert destination.read_text(encoding="utf-8") == "second\n"


def test_main_seeds_rules_before_registry_bootstrap(
    monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
    events: list[object] = []

    class FakeRegistry:
        def __init__(self, kind=systray_codex_switcher.AccountRegistryKind.CODEX) -> None:
            self.kind = kind
            events.append(f"registry:{kind.value}")

        def migrate_legacy(self) -> None:
            events.append("migrate")

        def list(self):
            events.append("list")
            return [type("Account", (), {"slug": "avi"})()]

        def default_slug(self) -> str:
            return "avi"

    monkeypatch.setattr(systray_codex_switcher, "TRAY_DIR", tmp_path)
    monkeypatch.setattr(
        systray_codex_switcher,
        "seed_routing_rules",
        lambda path, known_slugs=None, default_slug=None: events.append(("seed", path)),
    )
    monkeypatch.setattr(systray_codex_switcher, "AccountRegistry", FakeRegistry)
    monkeypatch.setattr(systray_codex_switcher, "AccountHealthClient", lambda: object())

    class FakeIndicator:
        def __init__(
            self,
            registry,
            health_client,
            claude_registry=None,
            grok_registry=None,
            provider_services=None,
            device_auth_flow=None,
            device_auth_operation=None,
        ) -> None:
            del provider_services, device_auth_flow, device_auth_operation
            events.append(
                f"indicator:{registry.kind.value}:{claude_registry.kind.value}:{grok_registry.kind.value}"
            )

        def build(self) -> None:
            events.append("build")

        def run(self) -> None:
            events.append("run")

    monkeypatch.setattr(systray_codex_switcher, "Indicator", FakeIndicator)

    class FakeGrokAuthOperation:
        def __init__(self, registry) -> None:
            del registry

        def import_from_legacy_home(self, *, slug: str, alias: str) -> None:
            del slug, alias

    monkeypatch.setattr(systray_codex_switcher, "GrokAuthOperation", FakeGrokAuthOperation)
    monkeypatch.setattr(systray_codex_switcher, "GrokHealthClient", lambda: object())

    systray_codex_switcher.main()

    assert events[:9] == [
        "registry:codex",
        "migrate",
        "list",
        ("seed", tmp_path / "routing_rules.json"),
        "registry:claude",
        "migrate",
        "list",
        ("seed", tmp_path / "claude_routing_rules.json"),
        "registry:grok",
    ]


def test_shell_shim_delegates_to_systray_suite(monkeypatch: pytest.MonkeyPatch) -> None:
    shim_path = Path(__file__)
    spec = importlib.util.spec_from_file_location("test_systray_codex_switcher_shim", shim_path)
    assert spec is not None
    assert spec.loader is not None
    module = importlib.util.module_from_spec(spec)
    sys.modules[spec.name] = module
    spec.loader.exec_module(module)

    pytest_call: dict[str, object] = {}

    def fake_pytest_main(args: list[str]) -> int:
        pytest_call["args"] = args
        return 0

    monkeypatch.setattr(module.pytest, "main", fake_pytest_main)

    assert module.main() == 0
    assert pytest_call["args"] == ["tests/test_systray_codex_switcher.py", "-v"]


def test_shell_shim_runs_when_invoked_via_shell() -> None:
    if os.environ.get("SYSTRAY_CODEX_SWITCHER_SHIM_ACTIVE") == "1":
        pytest.skip("shell shim subprocess test must not recurse under shim execution")

    repo_root = Path(__file__).resolve().parent.parent
    shim_path = repo_root / "tests" / "test_systray_codex_switcher.py"

    env = os.environ.copy()
    env["PYTHON"] = sys.executable

    result = subprocess.run(
        ["sh", str(shim_path)],
        cwd=repo_root,
        capture_output=True,
        text=True,
        check=False,
        env=env,
    )

    assert result.returncode == 0, result.stderr


def main() -> int:
    return pytest.main(["tests/test_systray_codex_switcher.py", "-v"])


if __name__ == "__main__":
    raise SystemExit(main())
