"""
FastMCP 传输模式测试。

测试 main.py 中实现的传输模式选择和启动功能。
"""
import pytest
import sys
import logging
from unittest.mock import patch, MagicMock
from io import StringIO
import os


class TestCommandLineParsing:
    """测试命令行参数解析。"""

    def test_parse_transport_stdio(self):
        """测试解析 --transport stdio 参数。"""
        from main import parse_args
        
        # 模拟命令行参数
        test_args = ["--transport", "stdio"]
        with patch("sys.argv", ["main.py"] + test_args):
            args = parse_args()
            assert args.transport == "stdio"

    def test_parse_transport_http(self):
        """测试解析 --transport http 参数。"""
        from main import parse_args
        
        test_args = ["--transport", "http"]
        with patch("sys.argv", ["main.py"] + test_args):
            args = parse_args()
            assert args.transport == "http"

    def test_parse_transport_sse(self):
        """测试解析 --transport sse 参数。"""
        from main import parse_args
        
        test_args = ["--transport", "sse"]
        with patch("sys.argv", ["main.py"] + test_args):
            args = parse_args()
            assert args.transport == "sse"

    def test_parse_port(self):
        """测试解析 --port 参数。"""
        from main import parse_args
        
        test_args = ["--port", "9000"]
        with patch("sys.argv", ["main.py"] + test_args):
            args = parse_args()
            assert args.port == 9000

    def test_default_transport(self):
        """测试默认传输模式为 stdio。"""
        from main import parse_args
        
        with patch("sys.argv", ["main.py"]):
            args = parse_args()
            assert args.transport == "stdio"


class TestArgumentValidation:
    """测试参数验证。"""

    def test_validate_port_out_of_range_high(self):
        """测试端口超出范围（大于 65535）会退出。"""
        from main import validate_args
        
        args = MagicMock()
        args.transport = "http"
        args.port = 70000
        
        with pytest.raises(SystemExit) as exc_info:
            validate_args(args)
        
        assert exc_info.value.code == 1

    def test_validate_port_out_of_range_low(self):
        """测试端口超出范围（小于 1）会退出。"""
        from main import validate_args
        
        args = MagicMock()
        args.transport = "http"
        args.port = 0
        
        with pytest.raises(SystemExit) as exc_info:
            validate_args(args)
        
        assert exc_info.value.code == 1

    def test_validate_port_valid(self):
        """测试有效端口不会引发错误。"""
        from main import validate_args
        
        args = MagicMock()
        args.transport = "http"
        args.port = 8000
        
        # 不应该抛出异常
        validate_args(args)

    def test_validate_stdio_with_port_warning(self):
        """测试 stdio 模式下使用端口参数会提示警告。"""
        from main import validate_args
        
        args = MagicMock()
        args.transport = "stdio"
        args.port = 8000
        
        # 捕获 stderr 输出
        with patch("sys.stderr", new_callable=StringIO) as mock_stderr:
            validate_args(args)
            output = mock_stderr.getvalue()
            assert "注意" in output or "忽略" in output


class TestTransportStartup:
    """测试传输模式启动。"""

    def test_stdio_mode(self):
        """测试 stdio 模式启动和通信。"""
        import subprocess
        import json

        # 使用 subprocess 启动，分离 stdin/stdout/stderr
        process = subprocess.Popen(
            [sys.executable, "main.py", "--transport", "stdio"],
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True
        )

        try:
            # 发送简单的初始化请求
            init_request = {
                "jsonrpc": "2.0",
                "id": 1,
                "method": "initialize",
                "params": {
                    "protocolVersion": "2024-11-05",
                    "capabilities": {},
                    "clientInfo": {
                        "name": "test-client",
                        "version": "1.0.0"
                    }
                }
            }

            # 写入请求
            if process.stdin:
                process.stdin.write(json.dumps(init_request) + "\n")
                process.stdin.flush()
                process.stdin.close()

            # 等待进程结束或超时
            try:
                process.wait(timeout=5)
            except subprocess.TimeoutExpired:
                process.kill()
                process.wait()

            # 验证 stderr 包含日志信息
            if process.stderr:
                stderr_output = process.stderr.read()
                assert len(stderr_output) > 0, "stdio 模式应该有日志输出"

        finally:
            # 清理进程
            if process.poll() is None:
                process.kill()
                process.wait()

    def test_http_mode(self):
        """测试 streamable-http 模式启动和通信。"""
        import subprocess
        import httpx
        import time

        # 启动 HTTP 服务器（port 8001）
        process = subprocess.Popen(
            [sys.executable, "main.py", "--transport", "http", "--port", "8001"],
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True
        )

        try:
            # 等待服务器启动（使用 retry 机制）
            max_retries = 10
            server_started = False

            for _ in range(max_retries):
                try:
                    # 尝试发送 HTTP 请求到 http://localhost:8001/mcp
                    httpx.get("http://localhost:8001/mcp", timeout=1.0)
                    # 服务器启动成功
                    server_started = True
                    break
                except (httpx.ConnectError, httpx.TimeoutException):
                    # 服务器未就绪，等待后重试
                    time.sleep(0.5)

            # 验证服务器成功启动
            assert server_started, "HTTP 服务器应该在 5 秒内启动"

        finally:
            # 清理进程
            if process.poll() is None:
                process.terminate()
                try:
                    process.wait(timeout=5)
                except subprocess.TimeoutExpired:
                    process.kill()
                    process.wait()

    def test_sse_mode(self):
        """测试 SSE 模式启动和连接。"""
        import subprocess
        import httpx
        import time

        # 启动 SSE 服务器（port 8002），使用 DEVNULL 避免输出缓冲问题
        process = subprocess.Popen(
            [sys.executable, "main.py", "--transport", "sse", "--port", "8002"],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL
        )

        try:
            # 先等待 2 秒让服务器启动（SSE 模式需要更长时间启动）
            time.sleep(2)

            # 等待服务器启动
            max_retries = 15
            server_started = False
            last_error = None

            for _ in range(max_retries):
                try:
                    # 使用 HEAD 请求检查端点可达性（避免等待 SSE 流）
                    response = httpx.head(
                        "http://localhost:8002/sse",
                        timeout=1.0
                    )
                    # SSE 端点应该返回 200 或 405（HEAD 不支持）
                    # 关键是服务器正在监听
                    assert response.status_code in [200, 405], f"端点应该返回 200 或 405，实际返回 {response.status_code}"
                    server_started = True
                    break
                except (httpx.ConnectError, httpx.TimeoutException) as e:
                    # 服务器未就绪，等待后重试
                    last_error = str(e)
                    time.sleep(0.5)

            # 验证服务器成功启动
            if not server_started:
                print(f"最后错误: {last_error}")
            assert server_started, "SSE 服务器应该在 10 秒内启动"

        finally:
            # 清理进程
            if process.poll() is None:
                process.terminate()
                try:
                    process.wait(timeout=5)
                except subprocess.TimeoutExpired:
                    process.kill()
                    process.wait()

    def test_default_endpoints(self):
        """测试默认端点路径配置。"""
        import subprocess
        import httpx
        import time

        # 测试 HTTP 模式默认端点 /mcp
        process = subprocess.Popen(
            [sys.executable, "main.py", "--transport", "http", "--port", "8003"],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL
        )

        try:
            time.sleep(2)

            # 使用 HEAD 请求验证端点可达性（/mcp 端点可能只支持 POST）
            response = httpx.head(
                "http://localhost:8003/mcp",
                timeout=1.0
            )
            # HEAD 可能返回 405（不支持）或 200
            # 关键是服务器正在监听该路径
            assert response.status_code in [200, 405], \
                f"HTTP 默认端点 /mcp 应返回 200 或 405，实际返回 {response.status_code}"

        finally:
            if process.poll() is None:
                process.terminate()
                try:
                    process.wait(timeout=5)
                except subprocess.TimeoutExpired:
                    process.kill()
                    process.wait()


class TestInvalidArguments:
    """测试无效参数处理。"""

    def test_invalid_transport(self):
        """测试无效传输模式会引发错误并返回非 0 退出码。"""
        import subprocess

        # 使用 subprocess.run 捕获输出和退出码
        process = subprocess.run(
            [sys.executable, "main.py", "--transport", "invalid"],
            capture_output=True,
            text=True,
            timeout=5
        )

        # 验证退出码为非 0（argparse 错误通常是 2）
        assert process.returncode != 0, f"无效 transport 应返回非 0 退出码，实际返回 {process.returncode}"

        # 验证 stderr 包含错误信息
        assert "invalid choice" in process.stderr or "invalid" in process.stderr.lower(), \
            f"stderr 应包含错误信息，实际输出: {process.stderr}"

    def test_invalid_port_non_numeric(self):
        """测试无效端口（非数字）会引发错误。"""
        import subprocess

        process = subprocess.run(
            [sys.executable, "main.py", "--transport", "http", "--port", "abc"],
            capture_output=True,
            text=True,
            timeout=5
        )

        # 验证退出码为 2（argparse 错误）或 1（验证错误）
        assert process.returncode != 0, f"无效端口应返回非 0 退出码，实际返回 {process.returncode}"

    def test_invalid_port_out_of_range(self):
        """测试无效端口（超出范围）会引发错误。"""
        import subprocess

        # 测试端口 0
        process = subprocess.run(
            [sys.executable, "main.py", "--transport", "http", "--port", "0"],
            capture_output=True,
            text=True,
            timeout=5
        )

        assert process.returncode == 1, f"端口 0 应返回退出码 1，实际返回 {process.returncode}"

        # 测试端口 70000
        process = subprocess.run(
            [sys.executable, "main.py", "--transport", "http", "--port", "70000"],
            capture_output=True,
            text=True,
            timeout=5
        )

        assert process.returncode == 1, f"端口 70000 应返回退出码 1，实际返回 {process.returncode}"


class TestLoggingConfiguration:
    """测试日志配置。"""

    def test_stdio_mode_logs_to_stderr(self):
        """测试 stdio 模式下日志输出到 stderr。"""
        import logging
        
        # stdio 模式应该使用 stderr
        with patch("sys.stderr", new_callable=StringIO):
            logging.basicConfig(
                level=logging.INFO,
                format="%(asctime)s %(levelname)s %(name)s %(message)s",
                handlers=[logging.StreamHandler(sys.stderr)],
            )
            # 验证日志配置成功
            logger = logging.getLogger()
            assert logger is not None

    def test_http_mode_logs_to_stdout(self):
        """测试 http 模式下日志输出到 stdout。"""
        import logging
        
        # http 模式应该使用 stdout
        with patch("sys.stdout", new_callable=StringIO):
            logging.basicConfig(
                level=logging.INFO,
                format="%(asctime)s %(levelname)s %(name)s %(message)s",
            )
            # 验证日志配置成功
            logger = logging.getLogger()
            assert logger is not None

    def test_log_level_from_env(self):
        """测试从 LOG_LEVEL 环境变量读取日志级别。"""
        # 设置环境变量
        original_level = os.getenv("LOG_LEVEL")
        os.environ["LOG_LEVEL"] = "DEBUG"
        
        try:
            log_level_str = os.getenv("LOG_LEVEL", "INFO")
            log_level = getattr(logging, log_level_str.upper(), logging.INFO)
            
            assert log_level == logging.DEBUG
        finally:
            # 恢复原始值
            if original_level is None:
                os.environ.pop("LOG_LEVEL", None)
            else:
                os.environ["LOG_LEVEL"] = original_level


class TestFastMCPIntegration:
    """测试 FastMCP 集成。"""

    def test_mcp_instance_exists(self):
        """验证 FastMCP 实例存在。"""
        from server import mcp
        assert mcp is not None
        assert mcp.name == "WebSearch Server"

    def test_mcp_run_method_exists(self):
        """验证 FastMCP.run() 方法存在。"""
        from server import mcp
        assert callable(mcp.run)
