"""Exercise documented commands from npm's actual bin links, without shell rc files."""

import json
from pathlib import Path
import plistlib
import re
import shlex
import shutil
import sys
import unittest

from packaged_command_support import ROOT, install_package, run, seed_pools


@unittest.skipUnless(shutil.which("node") and shutil.which("npm"), "Node/npm required")
class PackagedCommandsTests(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        install_package(cls)

    def test_global_commands_work_without_postinstall_or_shell_rc(self):
        for command in ("claude-multiacc", "claude-accounts", "codex-accounts", "multiacc-select"):
            with self.subTest(command=command):
                resolved = shutil.which(command, path=self.env["PATH"])
                self.assertEqual(resolved, str(self.prefix / "bin" / command))
                flag = "--version" if command == "multiacc-select" else "--help"
                result = run([command, flag], self.env, self.work)
                self.assertEqual(result.returncode, 0, result.stderr)
        for shell in ("bash", "zsh"):
            if not shutil.which(shell):
                continue
            result = run([shell, "-fc", "codex-accounts verify --help"], self.env, self.work)
            self.assertEqual(result.returncode, 0, result.stderr)
        for command in ("claude-accounts", "codex-accounts"):
            result = run([command, "list"], self.env, self.work)
            self.assertNotEqual(result.returncode, 0)
            self.assertIn("run claude-multiacc install first", result.stderr)

    def test_every_account_help_command_dispatches_directly_and_via_wrapper(self):
        for provider in ("claude", "codex"):
            direct = f"{provider}-accounts"
            help_result = run([str(self.package / "bin" / direct), "--help"], self.env, self.work)
            verbs = set(re.findall(rf"^  {direct} ([a-z-]+)", help_result.stdout, re.MULTILINE))
            self.assertGreaterEqual(len(verbs), 18)
            wrapper = ["claude-multiacc"] + (["codex"] if provider == "codex" else [])
            for prefix in ([direct], wrapper):
                for verb in sorted(verbs):
                    with self.subTest(prefix=prefix, verb=verb):
                        result = run(prefix + [verb, "--help"], self.env, self.work)
                        self.assertEqual(result.returncode, 0, result.stderr)
                        self.assertIn(f"{direct} {verb}", result.stdout)
                result = run(prefix + ["not-a-command", "--help"], self.env, self.work)
                self.assertNotEqual(result.returncode, 0)

    def test_wrapper_and_installer_help_have_no_side_effects(self):
        before = sorted(str(path) for path in self.home.rglob("*"))
        for verb in ("help", "install", "update", "uninstall", "doctor", "self-update", "codex", "select"):
            with self.subTest(verb=verb):
                result = run(["claude-multiacc", verb, "--help"], self.env, self.work)
                self.assertEqual(result.returncode, 0, result.stderr)
                self.assertRegex(result.stdout, r"(?i)usage")
        result = run([str(self.package / "install.sh"), "--help"], self.env, self.work)
        self.assertEqual(result.returncode, 0, result.stderr)
        self.assertEqual(before, sorted(str(path) for path in self.home.rglob("*")))

    def test_verify_and_json_reports_use_the_correct_pool(self):
        seed_pools(self)
        for provider in ("claude", "codex"):
            wrapper = ["claude-multiacc"] + (["codex"] if provider == "codex" else [])
            for prefix in ([f"{provider}-accounts"], wrapper):
                for verb in ("list", "status"):
                    result = run(prefix + [verb, "--json"], self.env, self.work)
                    self.assertEqual(result.returncode, 0, result.stderr)
                    self.assertEqual(json.loads(result.stdout)["provider"], provider)
                for flags, expected in ((["--quick"], ": OK"), ([], ": PASS")):
                    result = run(prefix + ["verify"] + flags, self.env, self.work)
                    self.assertEqual(result.returncode, 0, result.stderr)
                    self.assertIn(f"{provider}@example.test{expected}", result.stdout)
        for prefix in (["codex-accounts"], ["claude-multiacc", "codex"]):
            for check in ([], ["--check"]):
                result = run(prefix + ["configure", "--max-subagents", "8", "--include-global", "--json"]
                             + check, self.env, self.work)
                self.assertEqual(result.returncode, 0, result.stderr)
                report = json.loads(result.stdout)
                self.assertTrue(report["verified"])
                self.assertTrue(report["global_configured"])
                self.assertEqual(report["accounts_configured"], 1)
                self.assertEqual(report["plugin_version"],
                                 json.loads((self.package / "package.json").read_text())["version"])
                self.assertNotIn("acct-01", result.stdout)

    def test_self_update_uses_the_running_install_prefix(self):
        fake_npm = self.prefix / "bin/npm"
        original = fake_npm.read_text()
        fake_npm.write_text('#!/usr/bin/env bash\n'
                            'if [ "$1" = view ]; then echo 999.0.0; exit 0; fi\n'
                            'printf "%s\\n" "$@" > "$HOME/update-args"\nexit 42\n')
        fake_npm.chmod(0o755)
        try:
            result = run(["claude-multiacc", "self-update"], self.env, self.work)
            self.assertNotEqual(result.returncode, 0)
            args = (self.home / "update-args").read_text().splitlines()
            self.assertEqual(args, ["install", "-g", "--prefix", str(self.prefix), "claude-multiacc@latest"])
        finally:
            fake_npm.write_text(original)

    def test_git_and_npx_path_entrypoint_matches_npm(self):
        entrypoint = self.package / "bin/claude-multiacc"
        self.assertTrue(entrypoint.is_file())
        result = run([str(entrypoint), "--version"], self.env, self.work)
        self.assertEqual(result.returncode, 0, result.stderr)
        self.assertEqual(result.stdout.strip(), json.loads((self.package / "package.json").read_text())["version"])

    def test_installer_is_idempotent_and_instances_leave_system_commands_alone(self):
        uname = self.prefix / "bin/uname"
        identity = self.prefix / "bin/id"
        uname.write_text('#!/usr/bin/env bash\necho Darwin\n')
        identity.write_text('#!/usr/bin/env bash\necho 0\n')
        uname.chmod(0o755)
        identity.chmod(0o755)
        # bash/sh login shells read ~/.profile when ~/.bash_profile is absent, and a
        # ~/.bashrc exists on most machines: both must end with the block, ~/.bash_profile
        # must NOT be created (its existence would stop bash reading ~/.profile).
        prepend = 'export PATH="$HOME/.local/bin:$PATH"\n'
        for rc in (".profile", ".bashrc"):
            (self.home / rc).write_text(prepend)
        real_bin = self.home / ".local/bin"
        real_bin.mkdir(parents=True)
        for name in ("claude", "codex"):
            (real_bin / name).write_text('#!/bin/sh\necho REAL\n')
            (real_bin / name).chmod(0o755)
        try:
            for _ in range(2):
                result = run(["claude-multiacc", "install", "--no-server", "--no-schedule"], self.env, self.work)
                self.assertEqual(result.returncode, 0, result.stderr)
                self.assertNotIn("status |", result.stdout)
            for rc in (".zshenv", ".zprofile", ".zshrc", ".profile", ".bashrc"):
                text = (self.home / rc).read_text()
                self.assertEqual(text.count("# >>> claude-multiacc >>>"), 1, rc)
                self.assertEqual(text.count("_claude_multiacc_path_guard() {"), 1, rc)
                self.assertTrue(text.rstrip().endswith("# <<< claude-multiacc <<<"), rc)
            self.assertFalse((self.home / ".bash_profile").exists())
            # ...and fresh login shells of every flavour now resolve the package's shims,
            # the real binaries in ~/.local/bin notwithstanding (lib/shim_path.py is the
            # same probe `claude-accounts status` and `health` print).
            probe_env = {**self.env, "CLAUDE_MULTIACC_PATH_PROBE": "1", "USER": "tester"}
            result = run([sys.executable, str(self.package / "lib/shim_path.py"), str(self.package), "--json"],
                         probe_env, self.work)
            self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
            report = json.loads(result.stdout)
            self.assertEqual(report["bypassed"], [])
            answered = [m for m in report["modes"] if m["error"] != "shell not installed"]
            self.assertGreaterEqual(len(answered), 2)
            for mode in answered:
                self.assertIsNone(mode["error"], mode)
                for name in ("claude", "codex"):
                    self.assertTrue(mode["results"][name]["ok"], mode)
            status = run(["claude-accounts", "status"], probe_env, self.work)
            self.assertIn("shim on PATH", status.stdout)
            self.assertNotIn("BYPASSED", status.stdout)
            calls = (self.home / "tool-calls").read_text()
            self.assertNotRegex(calls, r"/(launchctl|crontab|ln) ")
            script = 'source "$0" --no-server --no-schedule; mac_schedule_install'
            result = run(["bash", "-c", script, str(self.package / "install.sh")], self.env, self.work)
            self.assertEqual(result.returncode, 0, result.stderr)
            plists = list((self.home / "Library/LaunchAgents").glob("*.plist"))
            self.assertEqual(len(plists), 5)
            for path in plists:
                job = plistlib.loads(path.read_bytes())
                self.assertIn("PATH", job["EnvironmentVariables"])
                self.assertTrue(Path(job["ProgramArguments"][0]).is_file())
            result = run(["claude-multiacc", "uninstall"], self.env, self.work)
            self.assertEqual(result.returncode, 0, result.stderr)
            for rc in (".zshenv", ".zprofile", ".zshrc", ".profile", ".bashrc"):
                self.assertNotIn("claude-multiacc", (self.home / rc).read_text(), rc)
            self.assertEqual((self.home / ".profile").read_text(), prepend)
            self.assertTrue((self.home / ".codex-accounts/accounts.json").exists())
            uname.write_text('#!/usr/bin/env bash\necho Linux\n')
            (self.home / "tool-calls").write_text("")
            args = ["install", "--instance", "sandbox", "--no-server", "--no-schedule"]
            result = run(["claude-multiacc"] + args, self.env, self.work)
            self.assertEqual(result.returncode, 0, result.stderr)
            self.assertNotIn("/ln ", (self.home / "tool-calls").read_text())
        finally:
            uname.unlink()
            identity.unlink()

    def test_selector_transport_and_version_match_across_entrypoints(self):
        expected = None
        for prefix in (["multiacc-select"], ["claude-multiacc", "select"]):
            version = run(prefix + ["--version"], self.env, self.work)
            self.assertEqual(version.returncode, 0, version.stderr)
            result = run(prefix + ["--request-json", "-", "--response-json", "-"], self.env, self.work, input="{}")
            self.assertEqual(result.returncode, 0, result.stderr)
            response = json.loads(result.stdout)
            self.assertEqual(response["selector_version"], version.stdout.strip())
            self.assertEqual(response["error_code"], "invalid_request")
            if expected is not None:
                self.assertEqual(response, expected)
            expected = response

    def test_documented_shell_examples_parse_and_command_names_exist(self):
        documents = [ROOT / "README.md", *sorted((ROOT / "docs").glob("*.md"))]
        for document in documents:
            for block in re.findall(r"```(?:bash|sh)\n(.*?)```", document.read_text(), re.DOTALL):
                result = run(["bash", "-n"], self.env, self.work, input=block)
                self.assertEqual(result.returncode, 0, f"{document.name}: {result.stderr}")
                for line in block.splitlines():
                    tokens = shlex.split(line, comments=True)
                    if not tokens or tokens[0] not in ("claude-accounts", "codex-accounts", "claude-multiacc"):
                        continue
                    self.assertNotIn("|", tokens, f"{document.name}: alternatives must be separate commands")
                    self.assertIsNone(re.search(r"--[a-z-]+#", line), line)
                    self.assertIsNotNone(shutil.which(tokens[0], path=self.env["PATH"]), line)


if __name__ == "__main__":
    unittest.main()
