"""Execution models — result types and context for step execution.

Provides ExecutionResult (the outcome of running one step),
ExecutionContext (shared state passed to each step), and
ExecutionError (exception type for execution failures).
"""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime
from typing import Any
from uuid import UUID, uuid4


@dataclass
class ExecutionContext:
    """Context for step execution.

    Carries run-level state shared across steps: the run ID, current
    step number, results from prior steps, accumulated output state,
    budget tracking, and arbitrary metadata.
    """

    run_id: UUID = field(default_factory=uuid4)
    step_number: int = 0
    previous_results: list[ExecutionResult] = field(default_factory=list)
    accumulated_state: dict[str, Any] = field(default_factory=dict)
    budget_used_usd: float = 0.0
    budget_limit_usd: float = 1.0
    start_time_ms: int = 0
    timeout_ms: int = 600_000
    metadata: dict[str, Any] = field(default_factory=dict)

    @property
    def successful_results(self) -> list[ExecutionResult]:
        return [r for r in self.previous_results if r.success]

    @property
    def failed_results(self) -> list[ExecutionResult]:
        return [r for r in self.previous_results if r.failed]

    @property
    def last_output(self) -> Any:
        if self.previous_results:
            for r in reversed(self.previous_results):
                if r.success and r.output is not None:
                    return r.output
        return None

    @property
    def budget_remaining(self) -> float:
        return max(0.0, self.budget_limit_usd - self.budget_used_usd)

    @property
    def is_over_budget(self) -> bool:
        return self.budget_used_usd > self.budget_limit_usd

    def to_dict(self) -> dict[str, Any]:
        return {
            "run_id": str(self.run_id),
            "step_number": self.step_number,
            "previous_count": len(self.previous_results),
            "budget_used_usd": self.budget_used_usd,
            "budget_limit_usd": self.budget_limit_usd,
        }


class ExecutionError(Exception):
    """Error during execution."""

    def __init__(
        self,
        message: str,
        step_number: int = 0,
        recoverable: bool = True,
        error_code: str = "",
    ) -> None:
        super().__init__(message)
        self.step_number = step_number
        self.recoverable = recoverable
        self.error_code = error_code


@dataclass
class StepTrace:
    """Detailed trace of a single step execution, for debugging."""

    step_number: int = 0
    tool: str = ""
    action: str = ""
    params_summary: str = ""
    started_at: datetime = field(default_factory=datetime.utcnow)
    duration_ms: int = 0
    success: bool = True
    error: str | None = None
    was_retried: bool = False
    used_fallback: bool = False

    def to_dict(self) -> dict[str, Any]:
        return {
            "step_number": self.step_number,
            "tool": self.tool,
            "action": self.action,
            "duration_ms": self.duration_ms,
            "success": self.success,
            "error": self.error,
            "was_retried": self.was_retried,
            "used_fallback": self.used_fallback,
        }


@dataclass
class ExecutionResult:
    """Result from executing a step."""

    id: UUID = field(default_factory=uuid4)
    step_number: int = 0
    success: bool = True
    output: Any = None
    error: str | None = None
    tool_used: str = ""
    duration_ms: int = 0
    retryable: bool = False
    was_retried: bool = False
    used_fallback: bool = False
    trace: StepTrace | None = None
    metadata: dict[str, Any] = field(default_factory=dict)
    created_at: datetime = field(default_factory=datetime.utcnow)

    @property
    def failed(self) -> bool:
        return not self.success

    def to_dict(self) -> dict[str, Any]:
        result = {
            "id": str(self.id),
            "step_number": self.step_number,
            "success": self.success,
            "error": self.error,
            "tool_used": self.tool_used,
            "duration_ms": self.duration_ms,
            "retryable": self.retryable,
            "was_retried": self.was_retried,
            "used_fallback": self.used_fallback,
        }
        if self.trace:
            result["trace"] = self.trace.to_dict()
        return result

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "ExecutionResult":
        return cls(
            id=UUID(data["id"]) if data.get("id") else uuid4(),
            step_number=data.get("step_number", 0),
            success=data.get("success", True),
            output=data.get("output"),
            error=data.get("error"),
            tool_used=data.get("tool_used", ""),
            duration_ms=data.get("duration_ms", 0),
            retryable=data.get("retryable", False),
            was_retried=data.get("was_retried", False),
            used_fallback=data.get("used_fallback", False),
        )
