"""Benchmark tests for MakePython module."""

from __future__ import annotations

import tempfile
from pathlib import Path
from unittest.mock import Mock, patch

import pytest

from sfi.makepython.makepython import (
    BuildTool,
    MakePythonConfig,
    SubprocessExecutor,
    _get_build_command,
)
from sfi.pyprojectparse.models.project import Project


@pytest.mark.benchmark(group="project_info")
def test_project_info_from_pyproject_small(benchmark) -> None:
    """Benchmark ProjectInfo creation from small pyproject.toml."""
    with tempfile.TemporaryDirectory() as temp_dir:
        project_dir = Path(temp_dir)
        pyproject_path = project_dir / "pyproject.toml"

        pyproject_content = """
[project]
name = "benchmark-test"
version = "1.0.0"

[build-system]
build-backend = "hatchling.build"
"""
        pyproject_path.write_text(pyproject_content)

        def create_project_info():
            return Project.from_toml_file(pyproject_path)

        result = benchmark.pedantic(create_project_info, iterations=1, rounds=10)
        assert result is not None
        assert result.name == "benchmark-test"


@pytest.mark.benchmark(group="project_info")
def test_project_info_from_pyproject_large(benchmark) -> None:
    """Benchmark ProjectInfo creation from large pyproject.toml."""
    with tempfile.TemporaryDirectory() as temp_dir:
        project_dir = Path(temp_dir)
        pyproject_path = project_dir / "pyproject.toml"

        # Create a larger pyproject.toml with more content
        pyproject_content = """
[project]
name = "large-benchmark-test"
version = "2.0.0"
description = "A test project with extensive configuration"
authors = [
    {name = "Test Author", email = "test@example.com"},
    {name = "Another Author", email = "another@example.com"}
]
license = {text = "MIT"}
readme = "README.md"
requires-python = ">=3.8"
dependencies = [
    "requests>=2.25.0",
    "click>=8.0.0",
    "pydantic>=1.8.0",
    "numpy>=1.20.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=6.0",
    "black>=21.0",
    "flake8>=3.8",
]
test = [
    "pytest>=6.0",
    "pytest-cov>=2.10",
    "pytest-benchmark>=3.4",
]

[build-system]
build-backend = "hatchling.build"
requires = ["hatchling>=1.0.0"]

[tool.hatch.version]
path = "src/package/__about__.py"

[tool.hatch.build.targets.sdist]
include = [
    "/src",
    "README.md",
    "LICENSE",
]

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
"""
        pyproject_path.write_text(pyproject_content)

        def create_project_info():
            return Project.from_toml_file(pyproject_path)

        result = benchmark(create_project_info)
        assert result is not None
        assert result.name == "large-benchmark-test"


@pytest.mark.benchmark(group="config")
def test_config_creation_and_caching(benchmark) -> None:
    """Benchmark configuration creation and property caching."""
    # Mock shutil.which to avoid system calls
    with patch("shutil.which") as mock_which:
        mock_which.side_effect = lambda x: x in ["uv", "poetry"]

        def create_and_access_config():
            config = MakePythonConfig()
            # Access cached property multiple times
            tools1 = config.supported_tools
            tools2 = config.supported_tools
            tools3 = config.supported_tools
            return len(tools1) + len(tools2) + len(tools3)

        result = benchmark.pedantic(create_and_access_config, iterations=1, rounds=10)
        assert isinstance(result, int)


@pytest.mark.benchmark(group="config")
def test_config_save_load_cycle(benchmark) -> None:
    """Benchmark configuration save/load cycle."""
    with tempfile.TemporaryDirectory() as temp_dir:
        config_file = Path(temp_dir) / "benchmark_config.json"

        def save_load_cycle():
            with patch("sfi.makepython.makepython.CONFIG_FILE", config_file):
                # Create and save
                config = MakePythonConfig(
                    default_build_tool=BuildTool.POETRY,
                    verbose_output=True,
                    max_retries=10,
                )
                config.save()

                # Load
                loaded_config = MakePythonConfig()
                return (
                    loaded_config.default_build_tool,
                    loaded_config.verbose_output,
                    loaded_config.max_retries,
                )

        result = benchmark(save_load_cycle)
        assert result == (BuildTool.POETRY, True, 10)


@pytest.mark.benchmark(group="command_detection")
def test_build_command_detection_simple(benchmark) -> None:
    """Benchmark simple build command detection."""
    with tempfile.TemporaryDirectory() as temp_dir:
        project_dir = Path(temp_dir)

        config = MakePythonConfig()

        def detect_build_command():
            return _get_build_command(project_dir, config)

        result = benchmark(detect_build_command)
        assert result is not None


@pytest.mark.benchmark(group="command_detection")
def test_build_command_detection_with_pyproject(benchmark) -> None:
    """Benchmark build command detection with pyproject.toml present."""
    with tempfile.TemporaryDirectory() as temp_dir:
        project_dir = Path(temp_dir)
        pyproject_path = project_dir / "pyproject.toml"

        pyproject_content = """
[project]
name = "test-project"

[build-system]
build-backend = "poetry.core.masonry.api"
"""
        pyproject_path.write_text(pyproject_content)

        config = MakePythonConfig()

        def detect_build_command():
            return _get_build_command(project_dir, config)

        result = benchmark(detect_build_command)
        assert result == "poetry"


@pytest.mark.benchmark(group="executor")
def test_subprocess_executor_performance(benchmark) -> None:
    """Benchmark subprocess executor performance."""
    config = MakePythonConfig(timeout_seconds=10)
    executor = SubprocessExecutor(config)

    # Mock subprocess to avoid actual execution
    with patch("subprocess.run") as mock_run:
        mock_result = Mock()
        mock_result.stdout = "benchmark output"
        mock_result.stderr = ""
        mock_run.return_value = mock_result

        def execute_command():
            return executor.execute(["echo", "benchmark"], Path("."))

        result = benchmark(execute_command)
        assert result == mock_result


@pytest.mark.benchmark(group="memory")
def test_memory_efficiency_small_files(benchmark) -> None:
    """Benchmark memory efficiency with small files."""
    with tempfile.TemporaryDirectory() as temp_dir:
        project_dir = Path(temp_dir)

        # Reduce number of files for faster testing
        for i in range(20):  # Reduced from 100 to 20
            subdir = project_dir / f"project_{i}"
            subdir.mkdir()
            pyproject_path = subdir / "pyproject.toml"
            pyproject_path.write_text(f"""
[project]
name = "project-{i}"
version = "0.1.{i}"
""")

        def process_all_projects():
            results = []
            for subdir in project_dir.iterdir():
                if subdir.is_dir():
                    pyproject_path = subdir / "pyproject.toml"
                    project = Project.from_toml_file(pyproject_path)
                    if project and project.name:
                        results.append(project.name)
            return len(results)

        result = benchmark.pedantic(process_all_projects, iterations=1, rounds=5)
        assert result == 20


@pytest.mark.benchmark(group="concurrent_operations")
def test_concurrent_config_access(benchmark) -> None:
    """Benchmark concurrent configuration access."""
    import concurrent.futures

    configs = []

    # Mock shutil.which to speed up config creation
    with patch("shutil.which") as mock_which:
        mock_which.side_effect = lambda x: x in ["uv", "poetry"]

        def create_configs():
            for _ in range(10):  # Reduced from 50 to 10
                config = MakePythonConfig()
                configs.append(config)
            return len(configs)

        def access_properties():
            results = []
            for config in configs:
                results.append(len(config.supported_tools))
            return sum(results)

        # First create configs
        num_configs = benchmark.pedantic(create_configs, iterations=1, rounds=5)
        assert num_configs == 50

        # Then benchmark property access
        with concurrent.futures.ThreadPoolExecutor(
            max_workers=5
        ) as executor:  # Reduced workers
            futures = [
                executor.submit(access_properties) for _ in range(5)
            ]  # Reduced futures
            results = [future.result() for future in futures]

        # All results should be the same
        assert len(set(results)) == 1


if __name__ == "__main__":
    pytest.main([__file__, "-v", "--benchmark-only"])
