"""Performance benchmarks for workflow engine module.

Benchmarks are excluded from regular test runs by default.
Run with: pytest tests/test_benchmark.py --benchmark-only -v
"""

import asyncio

import pytest

from sfi.workflowengine.workflowengine import (
    CPUTask,
    IOTask,
    ParallelTask,
    SerialTask,
    WorkflowEngine,
)


@pytest.fixture(scope="module")
def simple_workflow():
    """Create a simple workflow for benchmarking."""

    def create_workflow():
        engine = WorkflowEngine()
        engine.add_task(IOTask("A", 0.05))
        engine.add_task(IOTask("B", 0.05))
        engine.add_task(CPUTask("C", 10000, dependencies=["A"]))
        engine.add_task(IOTask("D", 0.05, dependencies=["A", "B"]))
        engine.add_task(IOTask("E", 0.05, dependencies=["C", "D"]))
        return engine

    return create_workflow


@pytest.fixture(scope="module")
def parallel_workflow():
    """Create a parallel-heavy workflow for benchmarking."""

    def create_workflow():
        engine = WorkflowEngine(max_concurrent=4)
        # Add 20 independent IO tasks
        for i in range(20):
            engine.add_task(IOTask(f"io_{i}", 0.02))
        return engine

    return create_workflow


@pytest.fixture(scope="module")
def complex_workflow():
    """Create a complex workflow with mixed task types for benchmarking."""

    def create_workflow():
        engine = WorkflowEngine(max_concurrent=4)

        # Phase 1: Parallel data fetching
        engine.add_task(IOTask("fetch_users", 0.03))
        engine.add_task(IOTask("fetch_products", 0.03))
        engine.add_task(IOTask("fetch_orders", 0.03))

        # Phase 2: Serial processing
        def process_data(inputs, state):
            return {"processed": True}

        engine.add_task(SerialTask("process", process_data, dependencies=["fetch_users", "fetch_products"]))

        # Phase 3: CPU computation
        engine.add_task(CPUTask("calculate_stats", 50000, dependencies=["process"]))
        engine.add_task(CPUTask("generate_report", 40000, dependencies=["process"]))

        # Phase 4: Parallel save
        engine.add_task(IOTask("save_db", 0.03, dependencies=["calculate_stats", "generate_report"]))
        engine.add_task(IOTask("send_notifications", 0.02, dependencies=["calculate_stats"]))

        return engine

    return create_workflow


@pytest.fixture(scope="module")
def cpu_intensive_workflow():
    """Create a CPU-intensive workflow for benchmarking."""

    def create_workflow():
        engine = WorkflowEngine(max_concurrent=4)
        engine.add_task(CPUTask("cpu_1", 100000))
        engine.add_task(CPUTask("cpu_2", 100000, dependencies=["cpu_1"]))
        engine.add_task(CPUTask("cpu_3", 100000, dependencies=["cpu_1"]))
        engine.add_task(CPUTask("cpu_4", 100000, dependencies=["cpu_2", "cpu_3"]))
        return engine

    return create_workflow


@pytest.fixture(scope="module")
def nested_parallel_workflow():
    """Create a workflow with nested parallel tasks for benchmarking."""

    def create_workflow():
        engine = WorkflowEngine(max_concurrent=3)

        # Main task
        engine.add_task(IOTask("main", 0.05))

        # Nested parallel task with many subtasks
        subtasks = [IOTask(f"sub_{i}", 0.02) for i in range(10)]
        engine.add_task(ParallelTask("parallel", subtasks, dependencies=["main"], max_concurrent=3))

        # Final task
        engine.add_task(IOTask("final", 0.05, dependencies=["parallel"]))

        return engine

    return create_workflow


# ==================== Concurrency Benchmarks ====================


@pytest.mark.benchmark(group="concurrency")
@pytest.mark.parametrize("max_concurrent", [1, 2, 4, 8, 16])
def test_benchmark_concurrency_levels(max_concurrent, benchmark, parallel_workflow):
    """Benchmark workflow execution with different concurrency levels."""

    def run_workflow():
        async def _async_run():
            engine = parallel_workflow()
            engine.max_concurrent = max_concurrent
            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark.pedantic(run_workflow, iterations=5, rounds=3)
    assert results is not None


@pytest.mark.benchmark(group="concurrency")
@pytest.mark.parametrize("max_concurrent", [1, 2, 4, 8])
def test_benchmark_concurrency_complex(max_concurrent, benchmark, complex_workflow):
    """Benchmark complex workflow with different concurrency levels."""

    def run_workflow():
        async def _async_run():
            engine = complex_workflow()
            engine.max_concurrent = max_concurrent
            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark.pedantic(run_workflow, iterations=3, rounds=3)
    assert results is not None


# ==================== Workflow Scale Benchmarks ====================


@pytest.mark.benchmark(group="scale")
@pytest.mark.parametrize("task_count", [5, 10, 20, 50, 100])
def test_benchmark_workflow_scale(task_count, benchmark):
    """Benchmark workflow execution with increasing task count."""

    def create_and_run_workflow():
        async def _async_run():
            engine = WorkflowEngine(max_concurrent=10)

            # Create independent tasks
            for i in range(task_count):
                engine.add_task(IOTask(f"task_{i}", 0.01))

            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark.pedantic(create_and_run_workflow, iterations=3, rounds=3)
    assert results is not None


@pytest.mark.benchmark(group="scale")
@pytest.mark.parametrize("dependency_depth", [1, 3, 5, 10])
def test_benchmark_dependency_depth(dependency_depth, benchmark):
    """Benchmark workflow with increasing dependency chain depth."""

    def create_and_run_workflow():
        async def _async_run():
            engine = WorkflowEngine(max_concurrent=1)

            # Create chain of dependencies: A -> B -> C -> ...
            engine.add_task(IOTask("task_0", 0.01))
            for i in range(1, dependency_depth):
                engine.add_task(IOTask(f"task_{i}", 0.01, dependencies=[f"task_{i - 1}"]))

            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark.pedantic(create_and_run_workflow, iterations=3, rounds=3)
    assert results is not None


# ==================== Task Type Benchmarks ====================


@pytest.mark.benchmark(group="task_types")
def test_benchmark_io_tasks(benchmark):
    """Benchmark pure IO task workflow."""

    def run_io_tasks():
        async def _async_run():
            engine = WorkflowEngine(max_concurrent=10)
            for i in range(20):
                engine.add_task(IOTask(f"io_{i}", 0.01))
            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark.pedantic(run_io_tasks, iterations=5, rounds=3)
    assert results is not None


@pytest.mark.benchmark(group="task_types")
def test_benchmark_cpu_tasks(benchmark):
    """Benchmark pure CPU task workflow."""

    def run_cpu_tasks():
        async def _async_run():
            engine = WorkflowEngine(max_concurrent=4)
            for i in range(5):
                engine.add_task(CPUTask(f"cpu_{i}", 50000))
            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark.pedantic(run_cpu_tasks, iterations=5, rounds=3)
    assert results is not None


@pytest.mark.benchmark(group="task_types")
def test_benchmark_serial_tasks(benchmark):
    """Benchmark serial task workflow."""

    def run_serial_tasks():
        async def _async_run():
            engine = WorkflowEngine(max_concurrent=4)

            def process_func(inputs, state):
                return {"result": True}

            for i in range(10):
                deps = [f"serial_{i - 1}"] if i > 0 else []
                engine.add_task(SerialTask(f"serial_{i}", process_func, dependencies=deps))

            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark.pedantic(run_serial_tasks, iterations=5, rounds=3)
    assert results is not None


@pytest.mark.benchmark(group="task_types")
def test_benchmark_parallel_tasks(benchmark):
    """Benchmark parallel task workflow."""

    def run_parallel_tasks():
        async def _async_run():
            engine = WorkflowEngine(max_concurrent=4)

            # Create parallel tasks with subtasks
            for i in range(3):
                subtasks = [IOTask(f"sub_{i}_{j}", 0.01) for j in range(5)]
                engine.add_task(ParallelTask(f"parallel_{i}", subtasks, max_concurrent=3))

            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark.pedantic(run_parallel_tasks, iterations=5, rounds=3)
    assert results is not None


@pytest.mark.benchmark(group="task_types")
@pytest.mark.parametrize("mix_ratio", [0.0, 0.25, 0.5, 0.75, 1.0])
def test_benchmark_mixed_tasks(mix_ratio, benchmark):
    """Benchmark workflow with different IO/CPU task ratios."""

    def run_mixed_tasks():
        async def _async_run():
            engine = WorkflowEngine(max_concurrent=4)

            total_tasks = 20
            cpu_count = int(total_tasks * mix_ratio)
            io_count = total_tasks - cpu_count

            for i in range(cpu_count):
                engine.add_task(CPUTask(f"cpu_{i}", 20000))
            for i in range(io_count):
                engine.add_task(IOTask(f"io_{i}", 0.01))

            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark.pedantic(run_mixed_tasks, iterations=3, rounds=3)
    assert results is not None


# ==================== CPU Intensity Benchmarks ====================


@pytest.mark.benchmark(group="cpu_intensity")
@pytest.mark.parametrize("iterations", [1000, 10000, 50000, 100000, 500000])
def test_benchmark_cpu_intensity(iterations, benchmark):
    """Benchmark CPU tasks with different intensity levels."""

    def run_cpu_task():
        async def _async_run():
            engine = WorkflowEngine(max_concurrent=2)
            engine.add_task(CPUTask("cpu_task", iterations))
            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark.pedantic(run_cpu_task, iterations=5, rounds=3)
    assert results is not None


@pytest.mark.benchmark(group="cpu_intensity")
def test_benchmark_cpu_intensive_workflow(benchmark, cpu_intensive_workflow):
    """Benchmark CPU-intensive workflow."""

    results = benchmark.pedantic(cpu_intensive_workflow, iterations=3, rounds=3)
    assert results is not None


# ==================== Dependency Complexity Benchmarks ====================


@pytest.mark.benchmark(group="dependency_complexity")
@pytest.mark.parametrize("fan_out", [1, 2, 4, 8])
def test_benchmark_fan_out_dependencies(fan_out, benchmark):
    """Benchmark workflow with different fan-out dependency patterns."""

    def run_fan_out_workflow():
        async def _async_run():
            engine = WorkflowEngine(max_concurrent=4)

            # Root task
            engine.add_task(IOTask("root", 0.01))

            # Fan out to multiple tasks
            for i in range(fan_out):
                deps = ["root"]
                engine.add_task(IOTask(f"fan_{i}", 0.01, dependencies=deps))

            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark.pedantic(run_fan_out_workflow, iterations=5, rounds=3)
    assert results is not None


@pytest.mark.benchmark(group="dependency_complexity")
@pytest.mark.parametrize("fan_in", [1, 2, 4, 8])
def test_benchmark_fan_in_dependencies(fan_in, benchmark):
    """Benchmark workflow with different fan-in dependency patterns."""

    def run_fan_in_workflow():
        async def _async_run():
            engine = WorkflowEngine(max_concurrent=4)

            # Multiple root tasks
            for i in range(fan_in):
                engine.add_task(IOTask(f"source_{i}", 0.01))

            # Fan in to single task
            deps = [f"source_{i}" for i in range(fan_in)]
            engine.add_task(IOTask("sink", 0.01, dependencies=deps))

            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark.pedantic(run_fan_in_workflow, iterations=5, rounds=3)
    assert results is not None


@pytest.mark.benchmark(group="dependency_complexity")
def test_benchmark_diamond_dependency(benchmark):
    """Benchmark diamond-shaped dependency pattern."""

    def run_diamond_workflow():
        async def _async_run():
            engine = WorkflowEngine(max_concurrent=4)

            # Diamond pattern: A -> B,C -> D
            engine.add_task(IOTask("A", 0.01))
            engine.add_task(IOTask("B", 0.01, dependencies=["A"]))
            engine.add_task(IOTask("C", 0.01, dependencies=["A"]))
            engine.add_task(IOTask("D", 0.01, dependencies=["B", "C"]))

            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark.pedantic(run_diamond_workflow, iterations=5, rounds=3)
    assert results is not None


# ==================== Nested Parallelism Benchmarks ====================


@pytest.mark.benchmark(group="nested_parallel")
@pytest.mark.parametrize("subtask_count", [5, 10, 20, 50])
def test_benchmark_nested_parallel_scale(subtask_count, benchmark):
    """Benchmark nested parallel tasks with increasing subtask counts."""

    def run_nested_parallel():
        async def _async_run():
            engine = WorkflowEngine(max_concurrent=3)
            engine.add_task(IOTask("main", 0.01))

            subtasks = [IOTask(f"sub_{i}", 0.01) for i in range(subtask_count)]
            engine.add_task(ParallelTask("parallel", subtasks, dependencies=["main"], max_concurrent=3))

            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark.pedantic(run_nested_parallel, iterations=3, rounds=3)
    assert results is not None


@pytest.mark.benchmark(group="nested_parallel")
def test_benchmark_deeply_nested_parallel(benchmark):
    """Benchmark deeply nested parallel tasks."""

    def run_deeply_nested():
        async def _async_run():
            engine = WorkflowEngine(max_concurrent=3)
            engine.add_task(IOTask("level_0", 0.01))

            # Create multiple levels of nesting
            prev_task = "level_0"
            for level in range(1, 4):
                subtasks = [IOTask(f"sub_{level}_{i}", 0.01) for i in range(3)]
                parallel_task = ParallelTask(f"level_{level}", subtasks, dependencies=[prev_task], max_concurrent=2)
                engine.add_task(parallel_task)
                prev_task = f"level_{level}"

            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark.pedantic(run_deeply_nested, iterations=3, rounds=3)
    assert results is not None


# ==================== Complete Workflow Benchmarks ====================


@pytest.mark.benchmark(group="complete_workflow")
def test_benchmark_simple_workflow(benchmark, simple_workflow):
    """Benchmark simple workflow execution."""

    results = benchmark.pedantic(simple_workflow, iterations=10, rounds=5)
    assert results is not None


@pytest.mark.benchmark(group="complete_workflow")
def test_benchmark_parallel_workflow(benchmark, parallel_workflow):
    """Benchmark parallel-heavy workflow execution."""

    results = benchmark.pedantic(parallel_workflow, iterations=5, rounds=3)
    assert results is not None


@pytest.mark.benchmark(group="complete_workflow")
def test_benchmark_complex_workflow(benchmark, complex_workflow):
    """Benchmark complex workflow execution."""

    results = benchmark.pedantic(complex_workflow, iterations=5, rounds=3)
    assert results is not None


@pytest.mark.benchmark(group="complete_workflow")
def test_benchmark_nested_parallel_workflow(benchmark, nested_parallel_workflow):
    """Benchmark nested parallel workflow execution."""

    results = benchmark.pedantic(nested_parallel_workflow, iterations=5, rounds=3)
    assert results is not None


# ==================== Comparison Benchmarks ====================


@pytest.mark.benchmark(group="comparison")
@pytest.mark.parametrize(
    "execution_mode,description", [("serial", "Serial execution"), ("parallel", "Parallel execution")]
)
def test_benchmark_serial_vs_parallel_io(execution_mode, description, benchmark):
    """Compare serial vs parallel execution for IO tasks."""

    max_concurrent = 1 if execution_mode == "serial" else 10

    def run_workflow():
        async def _async_run():
            engine = WorkflowEngine(max_concurrent=max_concurrent)
            for i in range(10):
                engine.add_task(IOTask(f"task_{i}", 0.02))
            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark(run_workflow)
    assert results is not None


@pytest.mark.benchmark(group="comparison")
@pytest.mark.parametrize(
    "workflow_type,description", [("single_phase", "Single-phase workflow"), ("multi_phase", "Multi-phase workflow")]
)
def test_benchmark_single_vs_multiple_phases(workflow_type, description, benchmark):
    """Compare single-phase vs multi-phase workflows."""

    def run_workflow():
        async def _async_run():
            engine = WorkflowEngine(max_concurrent=10)

            if workflow_type == "single_phase":
                # All tasks independent
                for i in range(15):
                    engine.add_task(IOTask(f"task_{i}", 0.01))
            else:
                # Create dependencies to force multiple phases
                engine.add_task(IOTask("phase1_a", 0.01))
                engine.add_task(IOTask("phase1_b", 0.01))
                engine.add_task(IOTask("phase2", 0.01, dependencies=["phase1_a", "phase1_b"]))
                engine.add_task(IOTask("phase3", 0.01, dependencies=["phase2"]))

            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark(run_workflow)
    assert results is not None


# ==================== Execution Order Calculation Benchmarks ====================


@pytest.mark.benchmark(group="execution_order")
@pytest.mark.parametrize("task_count", [10, 50, 100, 200, 500])
def test_benchmark_execution_order_calculation(task_count, benchmark):
    """Benchmark execution order calculation with different task counts."""

    def calculate_order():
        engine = WorkflowEngine()

        # Create tasks with random dependencies
        for i in range(task_count):
            deps = []
            # Each task depends on 1-2 random previous tasks
            if i > 0:
                import random

                num_deps = random.randint(1, min(2, i))
                deps = [f"task_{j}" for j in random.sample(range(i), num_deps)]
            engine.add_task(IOTask(f"task_{i}", 0.01, dependencies=deps))

        return engine.calculate_execution_order()

    results = benchmark.pedantic(calculate_order, iterations=5, rounds=3)
    assert results is not None


@pytest.mark.benchmark(group="execution_order")
def test_benchmark_dependency_validation(benchmark):
    """Benchmark dependency validation."""

    def validate_dependencies():
        engine = WorkflowEngine()

        # Create tasks with dependencies
        for i in range(50):
            deps = [f"task_{j}" for j in range(max(0, i - 2), i)]
            engine.add_task(IOTask(f"task_{i}", 0.01, dependencies=deps))

        return engine.validate_dependencies()

    results = benchmark.pedantic(validate_dependencies, iterations=10, rounds=5)
    assert results is not None


# ==================== Memory and Overhead Benchmarks ====================


@pytest.mark.benchmark(group="overhead")
@pytest.mark.parametrize("task_count", [10, 50, 100, 500])
def test_benchmark_workflow_initialization(task_count, benchmark):
    """Benchmark workflow engine initialization overhead."""

    def initialize_workflow():
        engine = WorkflowEngine(max_concurrent=10)
        for i in range(task_count):
            engine.add_task(IOTask(f"task_{i}", 0.01))
        return engine

    results = benchmark(initialize_workflow)
    assert results is not None


@pytest.mark.benchmark(group="overhead")
@pytest.mark.parametrize("task_count", [10, 50, 100])
def test_benchmark_task_creation(task_count, benchmark):
    """Benchmark task creation overhead."""

    def create_tasks():
        tasks = []
        for i in range(task_count):
            tasks.append(IOTask(f"task_{i}", 0.01))
        return tasks

    results = benchmark(create_tasks)
    assert results is not None


# ==================== Special Case Benchmarks ====================


@pytest.mark.benchmark(group="special_cases")
def test_benchmark_empty_workflow(benchmark):
    """Benchmark empty workflow execution."""

    def run_empty_workflow():
        async def _async_run():
            engine = WorkflowEngine()
            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark(run_empty_workflow)
    assert results is not None


@pytest.mark.benchmark(group="special_cases")
def test_benchmark_single_task_workflow(benchmark):
    """Benchmark single task workflow execution."""

    def run_single_task():
        async def _async_run():
            engine = WorkflowEngine()
            engine.add_task(IOTask("single_task", 0.01))
            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark(run_single_task)
    assert results is not None


@pytest.mark.benchmark(group="special_cases")
def test_benchmark_all_dependencies_satisfied(benchmark):
    """Benchmark workflow where all dependencies are already satisfied."""

    def run_pre_satisfied_workflow():
        async def _async_run():
            engine = WorkflowEngine(max_concurrent=4)

            # All tasks should be executable from the start
            for i in range(10):
                engine.add_task(IOTask(f"task_{i}", 0.01))

            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark.pedantic(run_pre_satisfied_workflow, iterations=5, rounds=3)
    assert results is not None


@pytest.mark.benchmark(group="special_cases")
def test_benchmark_strictly_sequential_dependencies(benchmark):
    """Benchmark workflow with strictly sequential dependencies."""

    def run_sequential_workflow():
        async def _async_run():
            engine = WorkflowEngine(max_concurrent=4)

            # Chain: A -> B -> C -> D -> ...
            engine.add_task(IOTask("task_0", 0.01))
            for i in range(1, 10):
                engine.add_task(IOTask(f"task_{i}", 0.01, dependencies=[f"task_{i - 1}"]))

            return await engine.execute_workflow()

        return asyncio.run(_async_run())

    results = benchmark.pedantic(run_sequential_workflow, iterations=5, rounds=3)
    assert results is not None


# ==================== Summary Benchmarks ====================


@pytest.mark.benchmark(group="summary")
def test_benchmark_execution_summary(benchmark):
    """Benchmark execution summary generation."""

    def run_and_summarize():
        async def _async_run():
            engine = WorkflowEngine(max_concurrent=4)

            # Create a mix of successful and failed tasks
            for i in range(15):
                engine.add_task(IOTask(f"task_{i}", 0.01))

            await engine.execute_workflow()
            return engine.get_execution_summary()

        return asyncio.run(_async_run())

    results = benchmark.pedantic(run_and_summarize, iterations=5, rounds=3)
    assert results is not None
