from __future__ import annotations

import importlib.util
import json
import os
import tempfile
import types
import unittest
from pathlib import Path
from unittest import mock


REPO_ROOT = Path(__file__).resolve().parents[1]
SERVER_PATH = REPO_ROOT / "python-cli-template" / "server.py"


def load_module():
    fake_fastmcp_module = types.ModuleType("mcp.server.fastmcp")

    class FakeFastMCP:
        def __init__(self, name: str):
            self.name = name

        def tool(self):
            def decorator(func):
                return func

            return decorator

        def run(self):
            return None

    fake_fastmcp_module.FastMCP = FakeFastMCP

    fake_server_module = types.ModuleType("mcp.server")
    fake_server_module.fastmcp = fake_fastmcp_module

    fake_mcp_module = types.ModuleType("mcp")
    fake_mcp_module.server = fake_server_module

    with mock.patch.dict(
        "sys.modules",
        {
            "mcp": fake_mcp_module,
            "mcp.server": fake_server_module,
            "mcp.server.fastmcp": fake_fastmcp_module,
        },
    ):
        spec = importlib.util.spec_from_file_location("server_template", SERVER_PATH)
        module = importlib.util.module_from_spec(spec)
        assert spec.loader is not None
        spec.loader.exec_module(module)
        return module


class ServerTemplateTests(unittest.TestCase):
    def test_load_dotenv_sets_missing_values_only(self):
        module = load_module()

        with tempfile.TemporaryDirectory() as tmp_dir:
            env_path = Path(tmp_dir) / ".env"
            env_path.write_text(
                "PROJECT_BIN=/tmp/from-dotenv\nPROJECT_CMD_TIMEOUT_SEC=90\n# comment\n",
                encoding="utf-8",
            )
            with mock.patch.dict(os.environ, {"PROJECT_BIN": "/tmp/already-set"}, clear=False):
                module._load_dotenv(env_path)
                self.assertEqual(os.environ["PROJECT_BIN"], "/tmp/already-set")
                self.assertEqual(os.environ["PROJECT_CMD_TIMEOUT_SEC"], "90")

    def test_version_reports_runtime_shape(self):
        module = load_module()

        with tempfile.TemporaryDirectory() as tmp_dir:
            tmp_path = Path(tmp_dir)
            fake_bin = tmp_path / "project-cli"
            fake_bin.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
            fake_bin.chmod(0o755)
            read_dir = tmp_path / "read"
            write_dir = tmp_path / "write"
            read_dir.mkdir()
            write_dir.mkdir()

            with mock.patch.dict(
                os.environ,
                {
                    "PROJECT_BIN": str(fake_bin),
                    "PROJECT_ALLOWED_READ_DIRS": str(read_dir),
                    "PROJECT_ALLOWED_WRITE_DIRS": str(write_dir),
                },
                clear=False,
            ):
                data = json.loads(module.version())

        self.assertEqual(data["server_name"], "project-template")
        self.assertEqual(data["template_version"], "2026.03.12")
        self.assertEqual(data["project_bin_env"], "PROJECT_BIN")
        self.assertEqual(data["resolved_bin"], str(fake_bin.resolve()))
        self.assertEqual(data["read_roots"], [str(read_dir.resolve())])
        self.assertEqual(data["write_roots"], [str(write_dir.resolve())])

    def test_paths_reports_runtime_paths(self):
        module = load_module()

        with tempfile.TemporaryDirectory() as tmp_dir:
            tmp_path = Path(tmp_dir)
            fake_bin = tmp_path / "project-cli"
            fake_bin.write_text("#!/bin/sh\nexit 0\n", encoding="utf-8")
            fake_bin.chmod(0o755)
            read_dir = tmp_path / "read"
            write_dir = tmp_path / "write"
            read_dir.mkdir()
            write_dir.mkdir()

            with mock.patch.dict(
                os.environ,
                {
                    "PROJECT_BIN": str(fake_bin),
                    "PROJECT_ALLOWED_READ_DIRS": str(read_dir),
                    "PROJECT_ALLOWED_WRITE_DIRS": str(write_dir),
                },
                clear=False,
            ):
                data = json.loads(module.paths())

        self.assertEqual(data["resolved_bin"], str(fake_bin))
        self.assertEqual(data["read_roots"], [str(read_dir.resolve())])
        self.assertEqual(data["write_roots"], [str(write_dir.resolve())])
        self.assertTrue(data["server_path"].endswith("server.py"))
        self.assertTrue(data["dotenv_path"].endswith(".env"))


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