"""Tests for process management: kill tree, child tracking, streaming runner."""

import os
import subprocess
import sys
from pathlib import Path
from unittest import mock

import process


class TestKillTree:
    def test_kills_real_process(self):
        proc = subprocess.Popen(
            [sys.executable, "-c", "import time; time.sleep(60)"],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )
        process.kill_tree(proc.pid)
        proc.wait(timeout=5)
        assert proc.returncode is not None

    def test_dead_pid_is_safe(self):
        process.kill_tree(999999999)


class TestChildPidTracking:
    def test_register_and_unregister(self):
        process.register_child_pid(99999)
        with process._active_child_lock:
            assert 99999 in process._active_child_pids
        process.unregister_child_pid(99999)
        with process._active_child_lock:
            assert 99999 not in process._active_child_pids

    def test_unregister_missing_is_noop(self):
        process.unregister_child_pid(88888)

    def test_kill_all_children_kills_tracked(self):
        proc = subprocess.Popen(
            [sys.executable, "-c", "import time; time.sleep(60)"],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )
        process.register_child_pid(proc.pid)
        process._kill_all_children()
        proc.wait(timeout=10)
        assert proc.returncode is not None
        process.unregister_child_pid(proc.pid)


class TestKillPreviousDaemon:
    def test_no_pid_file_is_noop(self, project_dir: Path):
        process.kill_previous_daemon(project_dir)

    def test_dead_pid_is_safe(self, project_dir: Path):
        pid_file = project_dir / ".autobuilder" / "daemon.pid"
        pid_file.write_text("999999999\n")
        process.kill_previous_daemon(project_dir)

    def test_own_pid_is_skipped(self, project_dir: Path):
        pid_file = project_dir / ".autobuilder" / "daemon.pid"
        pid_file.write_text(f"{os.getpid()}\n")
        process.kill_previous_daemon(project_dir)

    def test_kills_live_process(self, project_dir: Path):
        proc = subprocess.Popen(
            [sys.executable, "-c", "import time; time.sleep(60)"],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
        )
        pid_file = project_dir / ".autobuilder" / "daemon.pid"
        pid_file.write_text(f"{proc.pid}\n")
        process.kill_previous_daemon(project_dir)
        proc.wait(timeout=10)
        assert proc.returncode is not None


class TestCleanupDevServers:
    def test_is_noop(self):
        with mock.patch("process.subprocess.run") as mock_run:
            process.cleanup_dev_servers()
            mock_run.assert_not_called()


class TestRunStreaming:
    def test_captures_output(self, tmp_path: Path):
        log_file = tmp_path / "test.log"
        with open(log_file, "w") as fh:
            rc = process.run_streaming(
                [sys.executable, "-c", "print('hello from child')"],
                cwd=str(tmp_path),
                log_fh=fh,
                hard_timeout=30,
                label="test",
            )
        assert rc == 0
        assert "hello from child" in log_file.read_text()

    def test_returns_nonzero_on_failure(self, tmp_path: Path):
        log_file = tmp_path / "test.log"
        with open(log_file, "w") as fh:
            rc = process.run_streaming(
                [sys.executable, "-c", "import sys; sys.exit(42)"],
                cwd=str(tmp_path),
                log_fh=fh,
                hard_timeout=30,
                label="test",
            )
        assert rc == 42

    def test_hard_timeout_kills_process(self, tmp_path: Path):
        log_file = tmp_path / "test.log"
        with open(log_file, "w") as fh:
            rc = process.run_streaming(
                [sys.executable, "-c", "import time; time.sleep(300)"],
                cwd=str(tmp_path),
                log_fh=fh,
                hard_timeout=2,
                label="test",
            )
        assert rc == 124

    def test_done_callback_triggers_grace_kill(self, tmp_path: Path):
        log_file = tmp_path / "test.log"
        with open(log_file, "w") as fh:
            rc = process.run_streaming(
                [sys.executable, "-c", "import time; time.sleep(300)"],
                cwd=str(tmp_path),
                log_fh=fh,
                hard_timeout=60,
                label="test",
                done_callback=lambda: True,
                done_grace=2,
            )
        assert rc == 0
        assert "work complete" in log_file.read_text()
