"""Plan models — Plan, Step, Checkpoint.

A Plan is an ordered sequence of Steps derived from an Architect Path.
Steps carry tool, action, parameters, and checkpoint flags. Checkpoints
capture execution state so the engine can resume or rollback.
"""

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


@dataclass
class Checkpoint:
    """A checkpoint saved during execution."""

    id: UUID = field(default_factory=uuid4)
    step_number: int = 0
    state: dict[str, Any] = field(default_factory=dict)
    outputs: list[Any] = field(default_factory=list)
    created_at: datetime = field(default_factory=datetime.utcnow)

    def to_dict(self) -> dict[str, Any]:
        return {
            "id": str(self.id),
            "step_number": self.step_number,
            "state": self.state,
            "created_at": self.created_at.isoformat(),
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "Checkpoint":
        return cls(
            id=UUID(data["id"]) if data.get("id") else uuid4(),
            step_number=data.get("step_number", 0),
            state=data.get("state", {}),
            outputs=data.get("outputs", []),
        )


@dataclass
class Step:
    """A single step in an execution plan.

    Each step maps to one tool invocation. Steps carry:
    - tool + action + params for execution
    - checkpoint flag for state capture
    - idempotent flag for safe retry
    - optional fallback_tool for automatic alternative
    - dependencies on prior steps (by step number)
    """

    number: int = 0
    action: str = ""
    tool: str = ""
    params: dict[str, Any] = field(default_factory=dict)
    description: str = ""
    checkpoint: bool = False
    idempotent: bool = True
    timeout_ms: int = 30000
    fallback_tool: str | None = None
    fallback_params: dict[str, Any] | None = None
    depends_on: list[int] = field(default_factory=list)
    rollback_action: str | None = None
    rollback_params: dict[str, Any] | None = None
    retry_count: int = 0
    max_retries: int = 1

    @property
    def has_fallback(self) -> bool:
        return self.fallback_tool is not None

    @property
    def has_rollback(self) -> bool:
        return self.rollback_action is not None

    @property
    def can_retry(self) -> bool:
        return self.retry_count < self.max_retries

    def record_retry(self) -> None:
        self.retry_count += 1

    def to_dict(self) -> dict[str, Any]:
        result: dict[str, Any] = {
            "number": self.number,
            "action": self.action,
            "tool": self.tool,
            "params": self.params,
            "description": self.description,
            "checkpoint": self.checkpoint,
            "idempotent": self.idempotent,
            "timeout_ms": self.timeout_ms,
        }
        if self.fallback_tool:
            result["fallback_tool"] = self.fallback_tool
            result["fallback_params"] = self.fallback_params
        if self.depends_on:
            result["depends_on"] = self.depends_on
        if self.rollback_action:
            result["rollback_action"] = self.rollback_action
            result["rollback_params"] = self.rollback_params
        return result

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "Step":
        return cls(
            number=data.get("number", 0),
            action=data.get("action", ""),
            tool=data.get("tool", ""),
            params=data.get("params", {}),
            description=data.get("description", ""),
            checkpoint=data.get("checkpoint", False),
            idempotent=data.get("idempotent", True),
            timeout_ms=data.get("timeout_ms", 30000),
            fallback_tool=data.get("fallback_tool"),
            fallback_params=data.get("fallback_params"),
            depends_on=data.get("depends_on", []),
            rollback_action=data.get("rollback_action"),
            rollback_params=data.get("rollback_params"),
        )


@dataclass
class Plan:
    """An execution plan composed of steps."""

    id: UUID = field(default_factory=uuid4)
    goal_id: UUID | None = None
    path_id: str = ""
    steps: list[Step] = field(default_factory=list)
    tools_required: list[str] = field(default_factory=list)
    estimated_duration_ms: int = 0
    checkpoints: list[Checkpoint] = field(default_factory=list)
    status: str = "pending"  # pending, running, completed, failed, rolled_back
    created_at: datetime = field(default_factory=datetime.utcnow)

    @property
    def step_count(self) -> int:
        return len(self.steps)

    @property
    def checkpoint_count(self) -> int:
        return len(self.checkpoints)

    def get_step(self, number: int) -> Step | None:
        for s in self.steps:
            if s.number == number:
                return s
        return None

    def get_checkpoint_steps(self) -> list[Step]:
        return [s for s in self.steps if s.checkpoint]

    def validate(self) -> list[str]:
        """Validate plan integrity. Returns list of issues."""
        issues: list[str] = []
        if not self.steps:
            issues.append("Plan has no steps")
            return issues

        numbers = [s.number for s in self.steps]
        if len(numbers) != len(set(numbers)):
            issues.append("Duplicate step numbers found")

        for step in self.steps:
            if not step.tool:
                issues.append(f"Step {step.number} has no tool specified")
            for dep in step.depends_on:
                if dep not in numbers:
                    issues.append(
                        f"Step {step.number} depends on non-existent step {dep}"
                    )
                if dep >= step.number:
                    issues.append(
                        f"Step {step.number} depends on later step {dep}"
                    )

        return issues

    def to_dict(self) -> dict[str, Any]:
        return {
            "id": str(self.id),
            "goal_id": str(self.goal_id) if self.goal_id else None,
            "path_id": self.path_id,
            "steps": [s.to_dict() for s in self.steps],
            "tools_required": self.tools_required,
            "estimated_duration_ms": self.estimated_duration_ms,
            "checkpoints": [c.to_dict() for c in self.checkpoints],
            "status": self.status,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "Plan":
        return cls(
            id=UUID(data["id"]) if data.get("id") else uuid4(),
            goal_id=UUID(data["goal_id"]) if data.get("goal_id") else None,
            path_id=data.get("path_id", ""),
            steps=[Step.from_dict(s) for s in data.get("steps", [])],
            tools_required=data.get("tools_required", []),
            estimated_duration_ms=data.get("estimated_duration_ms", 0),
            checkpoints=[
                Checkpoint.from_dict(c) for c in data.get("checkpoints", [])
            ],
            status=data.get("status", "pending"),
        )
