"""
Graceful degradation for partial results when tasks fail.

This module provides mechanisms to collect partial results when some
tasks fail, allowing the system to continue with degraded functionality
rather than failing completely.

Requirements: EXEC-13 (retry), EXEC-14 (fallback), EXEC-15 (partial results)
"""

import logging
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Dict, List, Optional, Protocol, runtime_checkable

logger = logging.getLogger(__name__)


class TaskStatus(str, Enum):
    """Status of a task in the execution pipeline.

    Attributes:
        PENDING: Not yet started
        RUNNING: Currently executing
        COMPLETED: Successfully finished
        FAILED: Execution failed
        SKIPPED: Skipped due to dependency failure
    """

    PENDING = "pending"
    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"
    SKIPPED = "skipped"


@dataclass
class PartialResult:
    """Result collection with graceful degradation support.

    Tracks the status of all tasks in a wave, including successes,
    failures, and skipped tasks. Provides metrics for deciding whether
    execution should continue.

    Attributes:
        wave_id: The wave number this result belongs to
        total_tasks: Total number of tasks in the wave
        completed_tasks: Number of successfully completed tasks
        failed_tasks: Number of failed tasks
        skipped_tasks: Number of skipped tasks (due to dependency failures)
        results: Dictionary mapping task_id to successful result content
        errors: Dictionary mapping task_id to error messages
        task_status: Dictionary mapping task_id to TaskStatus
    """

    wave_id: int
    total_tasks: int
    completed_tasks: int = 0
    failed_tasks: int = 0
    skipped_tasks: int = 0
    results: Dict[str, Any] = field(default_factory=dict)
    errors: Dict[str, str] = field(default_factory=dict)
    task_status: Dict[str, TaskStatus] = field(default_factory=dict)

    @property
    def success_rate(self) -> float:
        """Calculate the success rate of completed tasks.

        Returns:
            Ratio of completed tasks to total tasks (0.0 if no tasks)
        """
        if self.total_tasks == 0:
            return 0.0
        return self.completed_tasks / self.total_tasks

    @property
    def is_partial_success(self) -> bool:
        """Check if this is a partial success (some succeeded, some failed).

        Returns:
            True if at least one task succeeded AND at least one failed
        """
        return self.completed_tasks > 0 and self.failed_tasks > 0

    @property
    def can_continue(self) -> bool:
        """Check if execution can continue to the next wave.

        Returns:
            True if at least one task succeeded (providing data for dependents)
        """
        return self.completed_tasks > 0


@runtime_checkable
class DependencyGraphProtocol(Protocol):
    """Protocol for dependency graph implementations.

    Any object with get_dependents() method can be used with GracefulDegradation.
    """

    def get_dependents(self, task_id: str) -> set:
        """Get all tasks that depend on the given task.

        Args:
            task_id: The task to find dependents for

        Returns:
            Set of task IDs that depend on this task
        """
        ...


class GracefulDegradation:
    """Handle partial failures gracefully with configurable thresholds.

    This class provides methods to collect partial results from task
    execution, determine whether execution should continue despite
    failures, and mark dependent tasks as skipped when their
    dependencies fail.

    Example:
        ```python
        from gsd_rlm.execution.degradation import GracefulDegradation

        gd = GracefulDegradation(min_success_rate=0.5)

        # Collect results from a wave
        partial = gd.collect_partial_results(wave_id=0, task_results=results)

        # Check if we should continue
        if gd.should_continue(partial):
            # Continue with next wave
            pass
        else:
            # Stop execution, return partial results
            pass
        ```
    """

    def __init__(self, min_success_rate: float = 0.5):
        """Initialize graceful degradation handler.

        Args:
            min_success_rate: Minimum success rate to consider wave successful.
                              Default 0.5 means at least 50% of tasks must succeed.
                              Note: should_continue() also returns True if ANY
                              task succeeded (for critical path continuation).
        """
        self.min_success_rate = min_success_rate

    def collect_partial_results(
        self,
        wave_id: int,
        task_results: List[Any],
    ) -> PartialResult:
        """Collect results from wave execution into a PartialResult.

        Analyzes task results to count successes, failures, and collect
        their outputs/errors.

        Args:
            wave_id: The wave number being processed
            task_results: List of task result objects (must have task_id,
                          success, content, and error attributes)

        Returns:
            PartialResult with full status of all tasks
        """
        result = PartialResult(
            wave_id=wave_id,
            total_tasks=len(task_results),
        )

        for tr in task_results:
            task_id = getattr(tr, "task_id", str(id(tr)))
            success = getattr(tr, "success", False)

            if success:
                result.completed_tasks += 1
                result.task_status[task_id] = TaskStatus.COMPLETED
                result.results[task_id] = getattr(tr, "content", None)
            else:
                result.failed_tasks += 1
                result.task_status[task_id] = TaskStatus.FAILED
                result.errors[task_id] = getattr(tr, "error", "Unknown error")

        return result

    def should_continue(self, partial: PartialResult) -> bool:
        """Determine if execution should continue despite failures.

        Uses two criteria:
        1. Success rate meets or exceeds min_success_rate, OR
        2. At least one task succeeded (critical path continuation)

        Args:
            partial: The PartialResult to evaluate

        Returns:
            True if execution should continue to next wave
        """
        if partial.total_tasks == 0:
            return False

        # Continue if success rate is acceptable
        if partial.success_rate >= self.min_success_rate:
            return True

        # Or if at least one task succeeded (for critical path)
        return partial.can_continue

    def mark_skipped_dependents(
        self,
        failed_task_id: str,
        dependency_graph: DependencyGraphProtocol,
        partial: PartialResult,
    ) -> None:
        """Mark all tasks that depend on a failed task as skipped.

        When a task fails, any tasks that depend on it cannot execute
        and should be marked as skipped. This updates the PartialResult
        in place.

        Args:
            failed_task_id: The ID of the task that failed
            dependency_graph: Object with get_dependents() method that
                              returns task IDs that depend on a given task
            partial: The PartialResult to update with skipped tasks
        """
        try:
            dependents = dependency_graph.get_dependents(failed_task_id)
        except Exception as e:
            logger.warning(f"Could not get dependents for {failed_task_id}: {e}")
            return

        for dep_id in dependents:
            current_status = partial.task_status.get(dep_id)
            # Only skip tasks that are still pending
            if current_status in (TaskStatus.PENDING, None):
                partial.task_status[dep_id] = TaskStatus.SKIPPED
                partial.skipped_tasks += 1
                partial.errors[dep_id] = (
                    f"Skipped: dependency '{failed_task_id}' failed"
                )
                logger.info(
                    f"Marked task {dep_id} as skipped (dep on {failed_task_id})"
                )
