from __future__ import annotations

import os
import sys
import json
import subprocess
import tempfile
import textwrap
from pathlib import Path
from typing import Awaitable, Callable

import pytest
from mcp.client.session import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client

from tests.conftest import _server_environment


async def _collect_logs(
    repo_root,
    env_overrides: dict[str, str],
    interaction: Callable[[ClientSession], Awaitable[None]],
) -> str:
    env = _server_environment(repo_root)
    env.update(env_overrides)

    with tempfile.TemporaryFile(mode="w+", encoding="utf-8") as errlog:
        server = StdioServerParameters(
            command=sys.executable,
            args=["-m", "lean_lsp_mcp", "--transport", "stdio"],
            env=env,
            cwd=str(repo_root),
        )

        async with stdio_client(server, errlog=errlog) as (read_stream, write_stream):
            session = ClientSession(read_stream, write_stream)
            async with session:
                await session.initialize()
                await interaction(session)

        errlog.seek(0)
        return errlog.read()


@pytest.mark.asyncio
async def test_no_stderr_when_log_level_none(repo_root, test_project_path) -> None:
    async def interaction(session: ClientSession) -> None:
        await session.list_tools()

    logs = await _collect_logs(
        repo_root,
        {
            "LEAN_LOG_LEVEL": "NONE",
            "LEAN_PROJECT_PATH": str(test_project_path),
        },
        interaction,
    )

    assert not logs.strip()


@pytest.mark.asyncio
async def test_info_level_emits_server_logs(repo_root, test_project_path) -> None:
    async def interaction(session: ClientSession) -> None:
        await session.list_tools()

    logs = await _collect_logs(
        repo_root,
        {
            "LEAN_LOG_LEVEL": "INFO",
            "LEAN_PROJECT_PATH": str(test_project_path),
        },
        interaction,
    )

    normalized = " ".join(logs.split())
    assert "Closing Lean LSP client" in normalized


@pytest.mark.asyncio
async def test_error_level_suppresses_info_logs(repo_root, test_project_path) -> None:
    async def interaction(session: ClientSession) -> None:
        await session.list_tools()

    logs = await _collect_logs(
        repo_root,
        {
            "LEAN_LOG_LEVEL": "ERROR",
            "LEAN_PROJECT_PATH": str(test_project_path),
        },
        interaction,
    )

    normalized = " ".join(logs.split())
    assert "Closing Lean LSP client" not in normalized


def test_logging_to_file(tmp_path):
    # Prepare config and log file paths
    cfg_path = tmp_path / "logcfg.json"
    log_file = tmp_path / "test.log"

    # Minimal dictConfig that writes INFO logs to the file
    cfg = {
        "version": 1,
        "formatters": {"simple": {"format": "%(levelname)s:%(message)s"}},
        "handlers": {
            "file": {
                "class": "logging.FileHandler",
                "level": "INFO",
                "formatter": "simple",
                "filename": str(log_file),
            }
        },
        "root": {"level": "INFO", "handlers": ["file"]},
    }
    cfg_path.write_text(json.dumps(cfg), encoding="utf-8")

    # Script executed in a subprocess: loads config from LEAN_LOG_FILE_CONFIG and logs
    script = textwrap.dedent(
        """
        import os, json, logging, logging.config
        cfg_file = os.environ["LEAN_LOG_FILE_CONFIG"]
        with open(cfg_file, "r", encoding="utf-8") as f:
            cfg = json.loads(f.read())
        logging.config.dictConfig(cfg)
        logging.getLogger(__name__).info("happy_path_log")
    """
    )

    env = os.environ.copy()
    env["LEAN_LOG_FILE_CONFIG"] = str(cfg_path)

    subprocess.run([sys.executable, "-c", script], check=True, env=env)

    content = Path(log_file).read_text(encoding="utf-8")
    assert "happy_path_log" in content


def test_logging_to_yaml_file(tmp_path):
    yaml = pytest.importorskip("yaml")

    cfg_path = tmp_path / "logcfg.yaml"
    log_file = tmp_path / "test_yaml.log"

    cfg = {
        "version": 1,
        "formatters": {"simple": {"format": "%(levelname)s:%(message)s"}},
        "handlers": {
            "file": {
                "class": "logging.FileHandler",
                "level": "INFO",
                "formatter": "simple",
                "filename": str(log_file),
            }
        },
        "root": {"level": "INFO", "handlers": ["file"]},
    }
    cfg_path.write_text(yaml.safe_dump(cfg), encoding="utf-8")

    script = textwrap.dedent(
        """
        import os, json, logging, logging.config
        import yaml
        cfg_file = os.environ["LEAN_LOG_FILE_CONFIG"]
        with open(cfg_file, "r", encoding="utf-8") as f:
            cfg = yaml.safe_load(f.read())
        logging.config.dictConfig(cfg)
        logging.getLogger(__name__).info("happy_path_yaml")
    """
    )

    env = os.environ.copy()
    env["LEAN_LOG_FILE_CONFIG"] = str(cfg_path)

    subprocess.run([sys.executable, "-c", script], check=True, env=env)

    content = Path(log_file).read_text(encoding="utf-8")
    assert "happy_path_yaml" in content


def test_logging_to_ini_file(tmp_path):
    cfg_path = tmp_path / "logcfg.ini"
    log_file = tmp_path / "test_ini.log"

    # fileConfig reads Python literal args, so ensure proper quoting
    args_literal = "({}, 'a')".format(repr(str(log_file)))

    ini = """
[loggers]
keys=root

[handlers]
keys=file

[formatters]
keys=simple

[logger_root]
level=INFO
handlers=file

[handler_file]
class=logging.FileHandler
level=INFO
formatter=simple
args={args}

[formatter_simple]
format=%(levelname)s:%(message)s
""".format(args=args_literal)

    cfg_path.write_text(ini, encoding="utf-8")

    script = textwrap.dedent(
        """
        import os, logging, logging.config
        cfg_file = os.environ["LEAN_LOG_FILE_CONFIG"]
        logging.config.fileConfig(cfg_file)
        logging.getLogger(__name__).info("happy_path_ini")
    """
    )

    env = os.environ.copy()
    env["LEAN_LOG_FILE_CONFIG"] = str(cfg_path)

    subprocess.run([sys.executable, "-c", script], check=True, env=env)

    content = Path(log_file).read_text(encoding="utf-8")
    assert "happy_path_ini" in content
