#!/usr/bin/env python3
"""Pure-function tests for seat_implementer_exec."""

from __future__ import annotations

import os
import sys
import tempfile
import unittest
import unittest.mock


_LIB = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "lib")
if _LIB not in sys.path:
    sys.path.insert(0, _LIB)

from seat_implementer_exec import (  # noqa: E402
    FORBIDDEN_ENV,
    build_arg_parser,
    build_implementer_env,
    require_root_transition,
    validate_args,
    validate_command,
    validate_operator_paths,
)
from seat_scope_entry import TMUX_CONF, write_tmux_conf  # noqa: E402

_OPERATOR_HOME = "/home/op"


class SeatImplementerExecTests(unittest.TestCase):
    _BASE_ARGS = [
        "--seat-id", "seat-x",
        "--seat-host", "debian1",
        "--seat-model", "gpt-5.6-terra",
        "--socket", "/tmp/s.sock",
        "--launcher", "/usr/local/bin/overdeck-seat-launcher",
        "--claude-bin", "/home/op/.local/bin/claude",
        "--guard-bin", "/home/op/.local/share/overdeck/seat-guard/current/bin",
        "--checkout", "/home/op/seats/seat-x/repo",
        "--state-dir", "/home/op/.local/state/overdeck/seats/seat-x",
        "--implementer-home", "/var/lib/overdeck/seat-runtime/seat-x/home",
        "--operator-home", _OPERATOR_HOME,
        "--account-slug", "roy",
        "--profile-dir", "/home/op/.claudex-accounts/roy/seat-x",
        "--secure-storage-dir", "/home/op/.claudex-accounts/roy/seat-x/secure-storage",
        "--netns-path", "/var/run/netns/overdeck-seat-seat-x",
    ]

    def _resolve_target(self) -> tuple[str, int, int, str]:
        return "op", 1000, 1000, _OPERATOR_HOME

    def test_build_implementer_env_strips_forbidden(self) -> None:
        parser = build_arg_parser()
        ns = parser.parse_args([*self._BASE_ARGS, "--", "tmux"])
        env = build_implementer_env(ns, 999, "ods-seat-x")
        for key in FORBIDDEN_ENV:
            self.assertNotIn(key, env)
        self.assertEqual(env["OVERDECK_CLAUDE_BIN"], ns.claude_bin)
        self.assertEqual(env["CLAUDE_CONFIG_DIR"], ns.profile_dir)
        self.assertEqual(env["HOME"], ns.implementer_home)
        self.assertNotEqual(env["HOME"], _OPERATOR_HOME)
        self.assertNotIn("GIT_CONFIG_GLOBAL", env)

    def test_validate_command_requires_tmux_conf(self) -> None:
        with self.assertRaises(SystemExit):
            validate_command(
                ["tmux", "-S", "/tmp/s.sock", "new-session", "--", "/bin/launcher"],
                "/bin/launcher",
                "/tmp/s.sock",
                "/tmp/tmux-seat.conf",
            )

    def test_write_tmux_conf_uses_control_path(self) -> None:
        with tempfile.TemporaryDirectory() as tmp:
            os.environ["OVERDECK_SEAT_CONTROL_BASE"] = os.path.join(tmp, "control")
            os.environ["OVERDECK_SEAT_TEST_MODE"] = "1"
            path = write_tmux_conf("seat-x", 1000, 1000)
            self.assertIn("/control/seat-x/tmux-seat.conf", path)
            with open(path, encoding="utf-8") as fh:
                text = fh.read()
            self.assertNotIn("systemd", text)
            self.assertEqual(text, TMUX_CONF)

    def test_validate_args_requires_separator(self) -> None:
        parser = build_arg_parser()
        ns = parser.parse_args([*self._BASE_ARGS, "tmux"])
        with unittest.mock.patch("seat_implementer_exec.resolve_target", side_effect=self._resolve_target):
            with self.assertRaises(SystemExit):
                validate_args(ns)

    def test_validate_operator_paths_rejects_literal_home(self) -> None:
        parser = build_arg_parser()
        ns = parser.parse_args([
            *self._BASE_ARGS,
            "--claude-bin", "$HOME/.local/bin/claude",
            "--",
            "tmux",
        ])
        with unittest.mock.patch("seat_implementer_exec.resolve_target", side_effect=self._resolve_target):
            with self.assertRaises(SystemExit):
                validate_args(ns)

    def test_validate_operator_paths_rejects_forged_operator_home(self) -> None:
        parser = build_arg_parser()
        ns = parser.parse_args([
            *self._BASE_ARGS,
            "--operator-home", "/home/forged",
            "--",
            "tmux",
        ])
        with unittest.mock.patch("seat_implementer_exec.resolve_target", side_effect=self._resolve_target):
            with self.assertRaises(SystemExit):
                validate_operator_paths(ns)

    def test_validate_operator_paths_rejects_mismatched_checkout(self) -> None:
        parser = build_arg_parser()
        ns = parser.parse_args([
            *self._BASE_ARGS,
            "--checkout", "/home/op/seats/seat-y/repo",
            "--",
            "tmux",
        ])
        with unittest.mock.patch("seat_implementer_exec.resolve_target", side_effect=self._resolve_target):
            with self.assertRaises(SystemExit):
                validate_operator_paths(ns)

    def test_validate_operator_paths_rejects_mismatched_profile(self) -> None:
        parser = build_arg_parser()
        ns = parser.parse_args([
            *self._BASE_ARGS,
            "--profile-dir", "/home/op/.claudex-accounts/wrong/seat-x",
            "--",
            "tmux",
        ])
        with unittest.mock.patch("seat_implementer_exec.resolve_target", side_effect=self._resolve_target):
            with self.assertRaises(SystemExit):
                validate_operator_paths(ns)

    def test_validate_operator_paths_rejects_mismatched_secure_storage(self) -> None:
        parser = build_arg_parser()
        ns = parser.parse_args([
            *self._BASE_ARGS,
            "--secure-storage-dir", "/home/op/.claudex-accounts/roy/seat-x/wrong",
            "--",
            "tmux",
        ])
        with unittest.mock.patch("seat_implementer_exec.resolve_target", side_effect=self._resolve_target):
            with self.assertRaises(SystemExit):
                validate_operator_paths(ns)

    def test_validate_operator_paths_accepts_exact_chain(self) -> None:
        parser = build_arg_parser()
        ns = parser.parse_args([*self._BASE_ARGS, "--", "tmux"])
        with unittest.mock.patch("seat_implementer_exec.resolve_target", side_effect=self._resolve_target):
            self.assertEqual(validate_operator_paths(ns), _OPERATOR_HOME)

    def test_require_root_transition_accepts_sudo_env(self) -> None:
        with unittest.mock.patch("seat_implementer_exec.os.geteuid", return_value=0):
            with unittest.mock.patch.dict(os.environ, {"SUDO_UID": "1000", "SUDO_USER": "fixture-user"}, clear=False):
                require_root_transition(1000)

    def test_require_root_transition_rejects_missing_sudo_env(self) -> None:
        with unittest.mock.patch("seat_implementer_exec.os.geteuid", return_value=0):
            with unittest.mock.patch.dict(os.environ, {}, clear=True):
                with self.assertRaises(SystemExit):
                    require_root_transition(1000)


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