from __future__ import annotations

import ast
import json
import os
import subprocess
import sys
from pathlib import Path

import pytest

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

import install


WRAPPER_CASES = (
    (
        "cdx.py",
        "routing_rules.json",
        "health_cache.json",
        "cdx",
        "rafa",
    ),
    (
        "cld.py",
        "claude_routing_rules.json",
        "claude_health_cache.json",
        "cld",
        "avi",
    ),
)


def _write_empty_account_setup(
    home: Path,
    *,
    rules_filename: str,
    health_filename: str,
    default_slug: str,
) -> None:
    tray_dir = home / ".local/state/overdeck/systray/runtime"
    tray_dir.mkdir(parents=True, exist_ok=True)
    (tray_dir / rules_filename).write_text(
        json.dumps(
            {
                "projects": {},
                "default": default_slug,
                "fallback_chain": [],
                "fallback_trigger": "broken_or_quota_exhausted",
                "quota_exhausted_threshold_pct": 100,
            }
        ),
        encoding="utf-8",
    )
    (tray_dir / health_filename).write_text("{}", encoding="utf-8")
    guard = home / ".claude" / "bin" / "local-dispatch-guard"
    guard.parent.mkdir(parents=True, exist_ok=True)
    guard.write_text("#!/usr/bin/bash\nexit 0\n", encoding="utf-8")
    guard.chmod(0o755)


@pytest.mark.parametrize(
    "script_name,rules_filename,health_filename,tool_name,default_slug",
    WRAPPER_CASES,
)
def test_installed_wrapper_exits_cleanly_without_accounts(
    tmp_path: Path,
    script_name: str,
    rules_filename: str,
    health_filename: str,
    tool_name: str,
    default_slug: str,
) -> None:
    repo_root = Path(__file__).resolve().parents[1]
    dest_bin = tmp_path / "bin"
    install.install(
        dest_bin=dest_bin,
        dest_apps=tmp_path / "applications",
        dest_icons=tmp_path / "icons",
    )

    entrypoint = dest_bin / script_name
    assert entrypoint.is_symlink()
    assert entrypoint.resolve() == repo_root / script_name

    home = tmp_path / "home"
    _write_empty_account_setup(
        home,
        rules_filename=rules_filename,
        health_filename=health_filename,
        default_slug=default_slug,
    )

    env = os.environ.copy()
    env["HOME"] = str(home)
    env["PYTHONPATH"] = (
        str(repo_root)
        if not env.get("PYTHONPATH")
        else os.pathsep.join((str(repo_root), env["PYTHONPATH"]))
    )

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

    assert result.returncode == 1
    assert result.stdout == ""
    assert result.stderr.strip().startswith(
        f"{tool_name}: no healthy account available: {default_slug}=unknown-account"
    )


def _main_guard_body(source: str) -> list[ast.stmt]:
    for node in ast.parse(source).body:
        if isinstance(node, ast.If) and ast.unparse(node.test) == "__name__ == '__main__'":
            return node.body
    return []


@pytest.mark.parametrize("script_name", ("cdx.py", "cld.py", "claudex.py"))
def test_entry_point_opts_into_desktop_notifications(script_name: str) -> None:
    """stall-guard notifications are opt-in; only a CLI a developer launched may opt in.

    Any other importer of stall_supervisor — test suites, gates, worktrees — stays silent.
    """
    source = (Path(__file__).resolve().parents[1] / script_name).read_text(encoding="utf-8")
    opt_ins = [
        ast.unparse(node)
        for node in ast.walk(ast.Module(body=_main_guard_body(source), type_ignores=[]))
        if isinstance(node, ast.Call) and "STALL_GUARD_NOTIFY" in ast.unparse(node)
    ]
    assert opt_ins == ["os.environ.setdefault('STALL_GUARD_NOTIFY', '1')"]
