"""Web UI 后台自动启动测试 (#209)"""

from __future__ import annotations

import json
import os
import subprocess
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

from index1.web import (
    _cleanup_pid_file,
    _is_port_open,
    _is_process_alive,
    _read_pid_file,
    _write_pid_file,
    ensure_web_running,
)


@pytest.fixture
def tmp_pid_file(tmp_path, monkeypatch):
    """临时 PID 文件路径"""
    pid_file = tmp_path / ".web_server.pid"
    monkeypatch.setattr("index1.web._WEB_PID_FILE", pid_file)
    monkeypatch.setattr("index1.web.INDEX1_HOME", tmp_path)
    return pid_file


class TestPortCheck:
    def test_port_not_open(self):
        # 用一个不太可能被占用的端口
        assert _is_port_open(19999) is False

    def test_port_check_handles_error(self):
        # 无效端口不崩溃
        assert _is_port_open(0) is False


class TestProcessAlive:
    def test_current_process_alive(self):
        assert _is_process_alive(os.getpid()) is True

    def test_nonexistent_pid(self):
        # 用一个极大的 PID，几乎不可能存在
        assert _is_process_alive(99999999) is False


class TestPidFile:
    def test_write_and_read(self, tmp_pid_file):
        _write_pid_file(12345, 6888)
        info = _read_pid_file()
        assert info is not None
        assert info["pid"] == 12345
        assert info["port"] == 6888
        assert "started_at" in info

    def test_read_nonexistent(self, tmp_pid_file):
        assert _read_pid_file() is None

    def test_read_corrupt(self, tmp_pid_file):
        tmp_pid_file.write_text("not json")
        assert _read_pid_file() is None

    def test_read_missing_pid(self, tmp_pid_file):
        tmp_pid_file.write_text(json.dumps({"port": 6888}))
        assert _read_pid_file() is None

    def test_cleanup(self, tmp_pid_file):
        _write_pid_file(12345, 6888)
        assert tmp_pid_file.exists()
        _cleanup_pid_file()
        assert not tmp_pid_file.exists()

    def test_cleanup_nonexistent(self, tmp_pid_file):
        # 清理不存在的文件不崩溃
        _cleanup_pid_file()


class TestEnsureWebRunning:
    def test_already_running_via_pid(self, tmp_pid_file):
        """PID 文件 + 进程存活 + 端口正常 → 跳过"""
        _write_pid_file(os.getpid(), 6888)
        with patch("index1.web._is_port_open", return_value=True):
            result = ensure_web_running()
        assert result is True

    def test_stale_pid_triggers_restart(self, tmp_pid_file):
        """PID 文件但进程不在 → 清理 + 重启"""
        _write_pid_file(99999999, 6888)
        mock_proc = MagicMock()
        mock_proc.pid = 11111
        mock_proc.poll.return_value = None  # 进程存活
        with (
            patch("index1.web._is_port_open", side_effect=[False, False, True]),
            patch("subprocess.Popen", return_value=mock_proc) as mock_popen,
        ):
            result = ensure_web_running()
        assert result is True
        mock_popen.assert_called_once()
        # 新 PID 文件被写入
        info = _read_pid_file()
        assert info["pid"] == 11111

    def test_fresh_start(self, tmp_pid_file):
        """无 PID 文件 → 启动新进程"""
        mock_proc = MagicMock()
        mock_proc.pid = 22222
        mock_proc.poll.return_value = None  # 进程存活
        with (
            patch("index1.web._is_port_open", side_effect=[False, False, True]),
            patch("subprocess.Popen", return_value=mock_proc) as mock_popen,
        ):
            result = ensure_web_running()
        assert result is True
        mock_popen.assert_called_once()
        # 检查启动参数
        args = mock_popen.call_args
        cmd = args[0][0]
        assert "web" in cmd
        assert args[1]["start_new_session"] is True

    def test_port_already_occupied(self, tmp_pid_file):
        """端口已被占用（手动启动的）→ 跳过"""
        with patch("index1.web._is_port_open", return_value=True):
            result = ensure_web_running()
        assert result is True
        # 没有 PID 文件，不尝试启动

    def test_disabled_by_config(self, tmp_pid_file):
        """web_auto_start=False 时不启动（在 hooks 层控制）"""
        from index1.config import Config

        config = Config(web_auto_start=False)
        # 即使 ensure_web_running 被调用也能工作
        # 但 hooks.py 层面会检查 config.web_auto_start
        # ensure_web_running 本身不检查此配置
        mock_proc = MagicMock()
        mock_proc.pid = 33333
        mock_proc.poll.return_value = None  # 进程存活
        with (
            patch("index1.web._is_port_open", side_effect=[False, False, True]),
            patch("subprocess.Popen", return_value=mock_proc),
        ):
            result = ensure_web_running(config)
        assert result is True

    def test_startup_failure(self, tmp_pid_file):
        """启动失败 → 返回 False 不崩溃"""
        with (
            patch("index1.web._is_port_open", return_value=False),
            patch("subprocess.Popen", side_effect=OSError("exec failed")),
        ):
            result = ensure_web_running()
        assert result is False

    def test_custom_port(self, tmp_pid_file):
        """自定义端口"""
        from index1.config import Config

        config = Config(web_port=7777)
        mock_proc = MagicMock()
        mock_proc.pid = 44444
        mock_proc.poll.return_value = None  # 进程存活
        with (
            patch("index1.web._is_port_open", side_effect=[False, False, True]),
            patch("subprocess.Popen", return_value=mock_proc) as mock_popen,
        ):
            result = ensure_web_running(config)
        assert result is True
        cmd = mock_popen.call_args[0][0]
        assert "7777" in cmd
        info = _read_pid_file()
        assert info["port"] == 7777
