"""
Parallel execution engine with AnyIO task groups.

This module provides the WaveRunner class for executing independent tasks
in parallel within waves, using AnyIO's structured concurrency primitives.

Requirements: EXEC-02 (parallel execution within waves)
"""

from typing import List, Callable, Optional, Dict, Any, Awaitable
from dataclasses import dataclass, field

import anyio


@dataclass
class TaskResult:
    """Result of a single task execution within a wave.

    Attributes:
        task_id: Unique identifier for the task
        content: The result content from task execution
        success: Whether the task completed successfully
        error: Error message if task failed, None otherwise
    """

    task_id: str
    content: Any
    success: bool
    error: Optional[str] = None


class WaveRunner:
    """Execute waves of independent tasks in parallel using AnyIO task groups.

    This class provides structured concurrency for parallel task execution.
    All tasks within a wave run concurrently, and the wave completes when
    all tasks finish (success or failure).

    Key features:
    - Uses AnyIO create_task_group() for structured concurrency
    - Collects all results including failures (doesn't fail fast)
    - Optional semaphore for limiting concurrent tasks
    - Progress callbacks for visibility

    Example:
        ```python
        import anyio
        from gsd_rlm.execution.parallel import WaveRunner

        async def main():
            runner = WaveRunner(max_concurrent=5)

            async def execute_task(task_id: str) -> str:
                await anyio.sleep(0.1)
                return f"Result of {task_id}"

            results = await runner.run_wave(
                tasks=["task-1", "task-2", "task-3"],
                executor=execute_task,
                on_progress=lambda status, task, detail: print(f"[{status}] {task}")
            )

            for result in results:
                print(f"{result.task_id}: {'OK' if result.success else 'FAILED'}")

        anyio.run(main)
        ```
    """

    def __init__(self, max_concurrent: int = 10):
        """Initialize the wave runner.

        Args:
            max_concurrent: Maximum number of tasks to run concurrently.
                           Uses a semaphore to limit parallelism.
        """
        self.max_concurrent = max_concurrent

    async def run_wave(
        self,
        tasks: List[str],
        executor: Callable[[str], Awaitable[Any]],
        on_progress: Optional[Callable[[str, str, str], None]] = None,
    ) -> List[TaskResult]:
        """Run all tasks in a wave concurrently.

        All tasks execute in parallel using AnyIO task groups. Failures
        are collected, not raised - the wave continues even if some tasks fail.

        Args:
            tasks: List of task IDs to execute
            executor: Async function that executes a single task and returns result
            on_progress: Optional callback for progress updates
                        Called as: on_progress(status, task_id, detail)

        Returns:
            List of TaskResult for each task, in the same order as input tasks

        Note:
            One task failing does NOT cancel other tasks. All results are
            collected and returned.
        """
        if not tasks:
            return []

        results: Dict[str, TaskResult] = {}

        async def run_single(task_id: str) -> None:
            """Execute a single task and store the result."""
            try:
                if on_progress:
                    on_progress("starting", task_id, "")

                content = await executor(task_id)

                results[task_id] = TaskResult(
                    task_id=task_id,
                    content=content,
                    success=True,
                    error=None,
                )

                if on_progress:
                    on_progress(
                        "completed", task_id, str(content)[:100] if content else ""
                    )

            except Exception as e:
                results[task_id] = TaskResult(
                    task_id=task_id,
                    content=None,
                    success=False,
                    error=str(e),
                )

                if on_progress:
                    on_progress("failed", task_id, str(e))

        async def run_with_semaphore(task_id: str, semaphore: anyio.Semaphore) -> None:
            """Execute a task with semaphore limiting."""
            async with semaphore:
                await run_single(task_id)

        # Run all tasks concurrently within the task group
        async with anyio.create_task_group() as tg:
            if self.max_concurrent > 0 and len(tasks) > self.max_concurrent:
                # Use semaphore to limit concurrency
                semaphore = anyio.Semaphore(self.max_concurrent)
                for task_id in tasks:
                    tg.start_soon(run_with_semaphore, task_id, semaphore)
            else:
                # No limit needed, run all directly
                for task_id in tasks:
                    tg.start_soon(run_single, task_id)

        # Return results in the same order as input tasks
        return [results[task_id] for task_id in tasks]
