"""Performance benchmarks for jinja2-async-environment.

These benchmarks establish baseline performance metrics for optimization work.
Run with: pytest tests/test_benchmarks.py::test_benchmark_* -v --benchmark-only
"""

import asyncio
from collections.abc import Generator
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import Any
from unittest.mock import AsyncMock, MagicMock

import pytest
from anyio import Path as AsyncPath
from jinja2.environment import Template
from pytest_benchmark.fixture import BenchmarkFixture
from jinja2_async_environment.bccache import AsyncBytecodeCache
from jinja2_async_environment.environment import (
    AsyncEnvironment,
    AsyncSandboxedEnvironment,
)
from jinja2_async_environment.loaders import (
    AsyncChoiceLoader,
    AsyncDictLoader,
    AsyncFileSystemLoader,
    AsyncFunctionLoader,
    AsyncPackageLoader,
)


@pytest.fixture
def temp_templates() -> Generator[AsyncPath]:
    """Create temporary template files for filesystem testing."""
    with TemporaryDirectory() as temp_dir:
        templates_dir = Path(temp_dir) / "templates"
        templates_dir.mkdir()

        # Create various template files
        (templates_dir / "simple.html").write_text("Hello {{ name }}!")
        (templates_dir / "complex.html").write_text("""
<!DOCTYPE html>
<html>
<head>
    <title>{{ title }}</title>
</head>
<body>
    <h1>{{ heading }}</h1>
    {% for item in items %}
        <p>{{ loop.index }}: {{ item.name }} - {{ item.value }}</p>
    {% endfor %}
    {% if show_footer %}
        <footer>{{ footer_text }}</footer>
    {% endif %}
</body>
</html>
        """)
        (templates_dir / "with_blocks.html").write_text("""
{% block header %}
    <header>Default Header</header>
{% endblock %}

{% block content %}
    <main>{{ content }}</main>
{% endblock %}

{% block footer %}
    <footer>Default Footer</footer>
{% endblock %}
        """)
        (templates_dir / "with_includes.html").write_text("""
{% include 'simple.html' %}
<div class="content">
    {{ main_content }}
</div>
        """)

        yield AsyncPath(templates_dir)


@pytest.fixture
def dict_templates() -> dict[str, str]:
    """Dictionary of templates for in-memory testing."""
    return {
        "simple.html": "Hello {{ name }}!",
        "complex.html": """
<!DOCTYPE html>
<html>
<head>
    <title>{{ title }}</title>
</head>
<body>
    <h1>{{ heading }}</h1>
    {% for item in items %}
        <p>{{ loop.index }}: {{ item.name }} - {{ item.value }}</p>
    {% endfor %}
    {% if show_footer %}
        <footer>{{ footer_text }}</footer>
    {% endif %}
</body>
</html>
        """,
        "with_blocks.html": """
{% block header %}
    <header>Default Header</header>
{% endblock %}

{% block content %}
    <main>{{ content }}</main>
{% endblock %}

{% block footer %}
    <footer>Default Footer</footer>
{% endblock %}
        """,
    }


@pytest.fixture
def complex_context() -> dict[str, Any]:
    """Complex context data for realistic rendering tests."""
    return {
        "title": "Performance Test Page",
        "heading": "Benchmark Results",
        "items": [{"name": f"Item {i}", "value": f"Value {i}"} for i in range(100)],
        "show_footer": True,
        "footer_text": "Generated by jinja2-async-environment",
        "content": "This is the main content area",
        "name": "Test User",
    }


class TestEnvironmentBenchmarks:
    """Benchmark core environment operations."""

    @pytest.mark.benchmark
    def test_benchmark_environment_creation(self, benchmark: BenchmarkFixture) -> None:
        """Benchmark AsyncEnvironment instantiation."""

        def create_environment():
            return AsyncEnvironment()

        result = benchmark(create_environment)
        assert isinstance(result, AsyncEnvironment)

    @pytest.mark.benchmark
    def test_benchmark_sandboxed_environment_creation(
        self, benchmark: BenchmarkFixture
    ) -> None:
        """Benchmark AsyncSandboxedEnvironment instantiation."""

        def create_sandbox_environment():
            return AsyncSandboxedEnvironment()

        result = benchmark(create_sandbox_environment)
        assert isinstance(result, AsyncSandboxedEnvironment)


class TestLoaderBenchmarks:
    """Benchmark different loader types."""

    @pytest.mark.benchmark
    def test_benchmark_filesystem_loader_creation(
        self, benchmark: BenchmarkFixture, temp_templates: AsyncPath
    ) -> None:
        """Benchmark AsyncFileSystemLoader instantiation."""

        def create_loader():
            return AsyncFileSystemLoader(temp_templates)

        result = benchmark(create_loader)
        assert isinstance(result, AsyncFileSystemLoader)

    @pytest.mark.benchmark
    def test_benchmark_dict_loader_creation(
        self, benchmark: BenchmarkFixture, dict_templates: dict[str, str]
    ) -> None:
        """Benchmark AsyncDictLoader instantiation."""

        def create_loader():
            return AsyncDictLoader(dict_templates, AsyncPath("/virtual"))

        result = benchmark(create_loader)
        assert isinstance(result, AsyncDictLoader)

    @pytest.mark.benchmark
    def test_benchmark_package_loader_creation(
        self, benchmark: BenchmarkFixture
    ) -> None:
        """Benchmark AsyncPackageLoader instantiation."""

        def create_loader():
            return AsyncPackageLoader(
                "jinja2_async_environment", AsyncPath("templates")
            )

        result = benchmark(create_loader)
        assert isinstance(result, AsyncPackageLoader)

    @pytest.mark.benchmark
    def test_benchmark_function_loader_creation(
        self, benchmark: BenchmarkFixture
    ) -> None:
        """Benchmark AsyncFunctionLoader instantiation."""

        async def load_func(name: str | AsyncPath):
            template_name = name.name if isinstance(name, AsyncPath) else name
            return f"Template {template_name}", f"path/{template_name}", lambda: True

        def create_loader():
            return AsyncFunctionLoader(load_func, AsyncPath("/function"))

        result = benchmark(create_loader)
        assert isinstance(result, AsyncFunctionLoader)

    @pytest.mark.benchmark
    def test_benchmark_choice_loader_creation(
        self,
        benchmark: BenchmarkFixture,
        temp_templates: AsyncPath,
        dict_templates: dict[str, str],
    ) -> None:
        """Benchmark AsyncChoiceLoader instantiation."""

        def create_loader():
            loaders = [
                AsyncFileSystemLoader(temp_templates),
                AsyncDictLoader(dict_templates, AsyncPath("/virtual")),
            ]
            return AsyncChoiceLoader(loaders, AsyncPath("/choice"))

        result = benchmark(create_loader)
        assert isinstance(result, AsyncChoiceLoader)


class TestTemplateBenchmarks:
    """Benchmark template operations."""

    @pytest.mark.benchmark
    def test_benchmark_simple_template_load(
        self, benchmark: BenchmarkFixture, dict_templates: dict[str, str]
    ) -> None:
        """Benchmark loading a simple template."""
        env = AsyncEnvironment(
            loader=AsyncDictLoader(dict_templates, AsyncPath("/virtual"))
        )

        async def load_template():
            return await env.get_template_async("simple.html")

        def run_benchmark():
            return asyncio.run(load_template())

        result = benchmark(run_benchmark)
        assert isinstance(result, Template)

    @pytest.mark.benchmark
    def test_benchmark_complex_template_load(
        self, benchmark: BenchmarkFixture, dict_templates: dict[str, str]
    ) -> None:
        """Benchmark loading a complex template."""
        env = AsyncEnvironment(
            loader=AsyncDictLoader(dict_templates, AsyncPath("/virtual"))
        )

        async def load_template():
            return await env.get_template_async("complex.html")

        def run_benchmark():
            return asyncio.run(load_template())

        result = benchmark(run_benchmark)
        assert isinstance(result, Template)

    @pytest.mark.benchmark
    def test_benchmark_template_render_simple(
        self, benchmark: BenchmarkFixture, dict_templates: dict[str, str]
    ) -> None:
        """Benchmark rendering a simple template."""

        async def setup_and_render():
            env = AsyncEnvironment(
                loader=AsyncDictLoader(dict_templates, AsyncPath("/virtual")),
                enable_async=True,
            )
            template = await env.get_template_async("simple.html")
            return await template.render_async(name="World")

        def run_benchmark():
            return asyncio.run(setup_and_render())

        result = benchmark(run_benchmark)
        assert "Hello World!" in result

    @pytest.mark.benchmark
    def test_benchmark_template_render_complex(
        self,
        benchmark: BenchmarkFixture,
        dict_templates: dict[str, str],
        complex_context: dict[str, Any],
    ) -> None:
        """Benchmark rendering a complex template."""

        async def setup_and_render():
            env = AsyncEnvironment(
                loader=AsyncDictLoader(dict_templates, AsyncPath("/virtual")),
                enable_async=True,
            )
            template = await env.get_template_async("complex.html")
            return await template.render_async(**complex_context)

        def run_benchmark():
            return asyncio.run(setup_and_render())

        result = benchmark(run_benchmark)
        assert "Performance Test Page" in result

    @pytest.mark.benchmark
    def test_benchmark_template_cache_hit(
        self, benchmark: BenchmarkFixture, dict_templates: dict[str, str]
    ) -> None:
        """Benchmark template loading with cache hits."""

        async def setup_and_load():
            env = AsyncEnvironment(
                loader=AsyncDictLoader(dict_templates, AsyncPath("/virtual"))
            )
            env.cache = {}

            # Pre-load template to populate cache
            await env.get_template_async("simple.html")

            # Load cached template
            return await env.get_template_async("simple.html")

        def run_benchmark():
            return asyncio.run(setup_and_load())

        result = benchmark(run_benchmark)
        assert isinstance(result, Template)

    @pytest.mark.benchmark
    def test_benchmark_template_cache_miss(
        self, benchmark: BenchmarkFixture, dict_templates: dict[str, str]
    ) -> None:
        """Benchmark template loading with cache misses."""
        template_names = ["simple.html", "complex.html", "with_blocks.html"]
        current_name = [0]

        async def load_uncached_template():
            env = AsyncEnvironment(
                loader=AsyncDictLoader(dict_templates, AsyncPath("/virtual"))
            )
            env.cache = {}

            name = template_names[current_name[0] % len(template_names)]
            current_name[0] += 1
            # Clear cache to ensure miss
            env.cache.clear()
            return await env.get_template_async(name)

        def run_benchmark():
            return asyncio.run(load_uncached_template())

        result = benchmark(run_benchmark)
        assert isinstance(result, Template)


class TestCacheBenchmarks:
    """Benchmark caching operations."""

    @pytest.mark.benchmark
    def test_benchmark_bytecode_cache_creation(
        self, benchmark: BenchmarkFixture
    ) -> None:
        """Benchmark AsyncBytecodeCache instantiation."""

        def create_cache():
            return AsyncBytecodeCache()

        result = benchmark(create_cache)
        assert isinstance(result, AsyncBytecodeCache)

    @pytest.mark.benchmark
    def test_benchmark_cache_key_generation(
        self, benchmark: BenchmarkFixture, dict_templates: dict[str, str]
    ) -> None:
        """Benchmark cache key generation."""
        env = AsyncEnvironment(
            loader=AsyncDictLoader(dict_templates, AsyncPath("/virtual"))
        )

        def generate_cache_key():
            from weakref import ref

            return (ref(env.loader), "test_template.html")

        result = benchmark(generate_cache_key)
        assert result is not None

    @pytest.mark.benchmark
    def test_benchmark_cache_operations(
        self, benchmark: BenchmarkFixture, dict_templates: dict[str, str]
    ) -> None:
        """Benchmark cache get/set operations."""

        async def setup():
            env = AsyncEnvironment(
                loader=AsyncDictLoader(dict_templates, AsyncPath("/virtual"))
            )
            env.cache = {}
            template = await env.get_template_async("simple.html")
            return env, template

        env, template = asyncio.run(setup())
        from weakref import ref

        cache_key = (ref(env.loader), "test_key")

        def cache_operations():
            cache = env.cache
            if cache is None:
                return None
            # Set
            cache[cache_key] = template
            # Get
            result = cache.get(cache_key)
            # Delete
            if cache_key in cache:
                del cache[cache_key]
            return result

        result = benchmark(cache_operations)
        assert result is template


class TestCompilationBenchmarks:
    """Benchmark template compilation operations."""

    @pytest.mark.benchmark
    def test_benchmark_simple_compilation(self, benchmark: BenchmarkFixture) -> None:
        """Benchmark compilation of simple Python code."""
        env = AsyncEnvironment()
        source_code = 'def template_func(): return "Hello World"'

        def compile_code():
            return env._compile(source_code, "test.py")

        result = benchmark(compile_code)
        assert result is not None

    @pytest.mark.benchmark
    def test_benchmark_complex_compilation(self, benchmark: BenchmarkFixture) -> None:
        """Benchmark compilation of complex template code."""
        env = AsyncEnvironment()
        source_code = """
async def template_func(context):
    for item in context.get("items", []):
        yield f"Item: {item}"
    if context.get("show_footer"):
        yield "Footer content"
"""

        def compile_code():
            return env._compile(source_code, "complex_template.py")

        result = benchmark(compile_code)
        assert result is not None

    @pytest.mark.benchmark
    def test_benchmark_async_yield_from(self, benchmark: BenchmarkFixture) -> None:
        """Benchmark _async_yield_from with different generator types."""
        env = AsyncEnvironment()

        def sync_generator():
            for i in range(10):
                yield f"Item {i}"

        async def async_test():
            return [item async for item in env._async_yield_from(sync_generator())]

        def run_async_yield_from():
            return asyncio.run(async_test())

        result = benchmark(run_async_yield_from)
        assert len(result) == 10


class TestTypeCheckingBenchmarks:
    """Benchmark type checking operations."""

    @pytest.mark.benchmark
    def test_benchmark_template_type_check(self, benchmark: BenchmarkFixture) -> None:
        """Benchmark template type checking."""
        AsyncEnvironment()
        template = MagicMock(spec=Template)

        def check_template_type():
            return (
                isinstance(template, Template)
                or str(type(template)).find("MagicMock") != -1
            )

        result = benchmark(check_template_type)
        assert result is True

    @pytest.mark.benchmark
    def test_benchmark_mock_detection(self, benchmark: BenchmarkFixture) -> None:
        """Benchmark mock object detection."""
        mock_obj = MagicMock()

        def detect_mock():
            return str(type(mock_obj)).find("MagicMock") != -1

        result = benchmark(detect_mock)
        assert result is True

    @pytest.mark.benchmark
    def test_benchmark_hasattr_check(self, benchmark: BenchmarkFixture) -> None:
        """Benchmark hasattr checks for loader capabilities."""
        loader = AsyncMock()
        loader.load_async = AsyncMock()

        def check_loader_capability():
            return hasattr(loader, "load_async")

        result = benchmark(check_loader_capability)
        assert result is True


class TestIntegrationBenchmarks:
    """End-to-end integration benchmarks."""

    @pytest.mark.benchmark
    def test_benchmark_full_template_workflow(
        self,
        benchmark: BenchmarkFixture,
        dict_templates: dict[str, str],
        complex_context: dict[str, Any],
    ) -> None:
        """Benchmark complete template workflow: load -> render."""

        async def full_workflow():
            # Create environment
            env = AsyncEnvironment(
                loader=AsyncDictLoader(dict_templates, AsyncPath("/virtual")),
                enable_async=True,
            )

            # Load template
            template = await env.get_template_async("complex.html")

            # Render template
            result = await template.render_async(**complex_context)

            return result

        def run_benchmark():
            return asyncio.run(full_workflow())

        result = benchmark(run_benchmark)
        assert "Performance Test Page" in result

    @pytest.mark.benchmark
    def test_benchmark_sandboxed_workflow(
        self, benchmark: BenchmarkFixture, dict_templates: dict[str, str]
    ) -> None:
        """Benchmark sandboxed environment workflow."""

        async def sandboxed_workflow():
            # Create sandboxed environment
            env = AsyncSandboxedEnvironment(
                loader=AsyncDictLoader(dict_templates, AsyncPath("/virtual")),
                enable_async=True,
            )

            # Load and render template
            template = await env.get_template_async("simple.html")
            result = await template.render_async(name="Sandbox Test")

            return result

        def run_benchmark():
            return asyncio.run(sandboxed_workflow())

        result = benchmark(run_benchmark)
        assert "Hello Sandbox Test!" in result

    @pytest.mark.benchmark
    def test_benchmark_choice_loader_workflow(
        self,
        benchmark: BenchmarkFixture,
        temp_templates: AsyncPath,
        dict_templates: dict[str, str],
    ) -> None:
        """Benchmark choice loader with fallback."""

        async def choice_loader_workflow():
            # Create choice loader (filesystem first, dict fallback)
            choice_loader = AsyncChoiceLoader(
                [
                    AsyncFileSystemLoader(temp_templates),
                    AsyncDictLoader(dict_templates, AsyncPath("/virtual")),
                ],
                AsyncPath("/choice"),
            )

            env = AsyncEnvironment(loader=choice_loader, enable_async=True)

            # Load template (will hit filesystem first)
            template = await env.get_template_async("simple.html")
            result = await template.render_async(name="Choice Test")

            return result

        def run_benchmark():
            return asyncio.run(choice_loader_workflow())

        result = benchmark(run_benchmark)
        assert "Hello Choice Test!" in result
