"""
Workflow Models - Dataclasses for workflow definitions.

Supports JSON/YAML workflow definitions with:
- Sequential steps
- Parallel execution groups
- Conditional branching
- Variable references
"""

import json
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Any, Optional


class StepType(Enum):
    """Types of workflow steps."""

    TASK = "task"  # Execute a task via agent
    PARALLEL = "parallel"  # Execute multiple steps in parallel
    CONDITIONAL = "conditional"  # Branch based on condition
    WAIT = "wait"  # Wait for condition or timeout
    APPROVAL = "approval"  # Wait for human approval
    VOTE = "vote"  # Multi-agent voting decision


class StepStatus(Enum):
    """Status of a workflow step."""

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


class WorkflowStatus(Enum):
    """Status of a workflow."""

    DRAFT = "draft"
    RUNNING = "running"
    PAUSED = "paused"
    COMPLETED = "completed"
    FAILED = "failed"
    CANCELLED = "cancelled"


class ConditionOperator(Enum):
    """Operators for conditional evaluation."""

    EQUALS = "eq"
    NOT_EQUALS = "ne"
    GREATER_THAN = "gt"
    LESS_THAN = "lt"
    GREATER_EQUAL = "gte"
    LESS_EQUAL = "lte"
    CONTAINS = "contains"
    NOT_CONTAINS = "not_contains"
    IS_TRUE = "is_true"
    IS_FALSE = "is_false"
    EXISTS = "exists"
    NOT_EXISTS = "not_exists"


@dataclass
class Condition:
    """A condition for branching logic."""

    variable: str  # Variable to check (e.g., "steps.build.status")
    operator: str  # ConditionOperator value
    value: Any = None  # Value to compare against

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return {
            "variable": self.variable,
            "operator": self.operator,
            "value": self.value,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "Condition":
        """Create from dictionary."""
        return cls(
            variable=data["variable"],
            operator=data["operator"],
            value=data.get("value"),
        )

    def evaluate(self, context: dict[str, Any]) -> bool:
        """Evaluate the condition against a context."""
        # Get variable value from context using dot notation
        actual = _get_nested_value(context, self.variable)

        op = self.operator
        if op == ConditionOperator.EQUALS.value:
            return actual == self.value
        elif op == ConditionOperator.NOT_EQUALS.value:
            return actual != self.value
        elif op == ConditionOperator.GREATER_THAN.value:
            return actual is not None and actual > self.value
        elif op == ConditionOperator.LESS_THAN.value:
            return actual is not None and actual < self.value
        elif op == ConditionOperator.GREATER_EQUAL.value:
            return actual is not None and actual >= self.value
        elif op == ConditionOperator.LESS_EQUAL.value:
            return actual is not None and actual <= self.value
        elif op == ConditionOperator.CONTAINS.value:
            return self.value in actual if actual else False
        elif op == ConditionOperator.NOT_CONTAINS.value:
            return self.value not in actual if actual else True
        elif op == ConditionOperator.IS_TRUE.value:
            return bool(actual) is True
        elif op == ConditionOperator.IS_FALSE.value:
            return bool(actual) is False
        elif op == ConditionOperator.EXISTS.value:
            return actual is not None
        elif op == ConditionOperator.NOT_EXISTS.value:
            return actual is None
        else:
            return False


@dataclass
class WorkflowStep:
    """A single step in a workflow."""

    id: str
    name: str
    step_type: str = "task"  # StepType value
    description: Optional[str] = None

    # For task steps
    task_type: Optional[str] = None
    task_description: Optional[str] = None
    agent_id: Optional[str] = None  # Specific agent or None for auto-assign
    timeout_minutes: int = 60

    # For parallel steps
    parallel_steps: list["WorkflowStep"] = field(default_factory=list)

    # For conditional steps
    condition: Optional[Condition] = None
    on_true: Optional["WorkflowStep"] = None
    on_false: Optional["WorkflowStep"] = None

    # For wait steps
    wait_for: Optional[str] = None  # Variable to wait for
    wait_timeout_minutes: int = 30

    # For approval steps
    approval_message: Optional[str] = None
    approvers: list[str] = field(default_factory=list)

    # For vote steps
    vote_topic: Optional[str] = None
    vote_options: list[str] = field(default_factory=list)
    vote_quorum: int = 3

    # Execution state
    status: str = "pending"  # StepStatus value
    started_at: Optional[datetime] = None
    completed_at: Optional[datetime] = None
    result: Optional[dict[str, Any]] = None
    error: Optional[str] = None

    # Dependencies
    depends_on: list[str] = field(default_factory=list)  # Step IDs to wait for

    # Retry configuration
    retry_count: int = 0
    max_retries: int = 0
    retry_delay_seconds: int = 30

    # Output mapping
    outputs: dict[str, str] = field(default_factory=dict)  # name -> JSONPath

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for serialization."""
        data = {
            "id": self.id,
            "name": self.name,
            "type": self.step_type,
        }

        if self.description:
            data["description"] = self.description

        if self.step_type == "task":
            data["task"] = {
                "type": self.task_type,
                "description": self.task_description,
                "agent_id": self.agent_id,
                "timeout_minutes": self.timeout_minutes,
            }
        elif self.step_type == "parallel":
            data["parallel"] = [s.to_dict() for s in self.parallel_steps]
        elif self.step_type == "conditional":
            data["condition"] = self.condition.to_dict() if self.condition else None
            data["on_true"] = self.on_true.to_dict() if self.on_true else None
            data["on_false"] = self.on_false.to_dict() if self.on_false else None
        elif self.step_type == "wait":
            data["wait"] = {
                "for": self.wait_for,
                "timeout_minutes": self.wait_timeout_minutes,
            }
        elif self.step_type == "approval":
            data["approval"] = {
                "message": self.approval_message,
                "approvers": self.approvers,
            }
        elif self.step_type == "vote":
            data["vote"] = {
                "topic": self.vote_topic,
                "options": self.vote_options,
                "quorum": self.vote_quorum,
            }

        if self.depends_on:
            data["depends_on"] = self.depends_on
        if self.max_retries > 0:
            data["retry"] = {
                "max": self.max_retries,
                "delay_seconds": self.retry_delay_seconds,
            }
        if self.outputs:
            data["outputs"] = self.outputs

        return data

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "WorkflowStep":
        """Create from dictionary."""
        step = cls(
            id=data["id"],
            name=data["name"],
            step_type=data.get("type", "task"),
            description=data.get("description"),
        )

        # Parse task config
        if "task" in data:
            task = data["task"]
            step.task_type = task.get("type")
            step.task_description = task.get("description")
            step.agent_id = task.get("agent_id")
            step.timeout_minutes = task.get("timeout_minutes", 60)

        # Parse parallel steps
        if "parallel" in data:
            step.parallel_steps = [
                WorkflowStep.from_dict(s) for s in data["parallel"]
            ]

        # Parse conditional
        if "condition" in data and data["condition"]:
            step.condition = Condition.from_dict(data["condition"])
        if "on_true" in data and data["on_true"]:
            step.on_true = WorkflowStep.from_dict(data["on_true"])
        if "on_false" in data and data["on_false"]:
            step.on_false = WorkflowStep.from_dict(data["on_false"])

        # Parse wait config
        if "wait" in data:
            wait = data["wait"]
            step.wait_for = wait.get("for")
            step.wait_timeout_minutes = wait.get("timeout_minutes", 30)

        # Parse approval config
        if "approval" in data:
            approval = data["approval"]
            step.approval_message = approval.get("message")
            step.approvers = approval.get("approvers", [])

        # Parse vote config
        if "vote" in data:
            vote = data["vote"]
            step.vote_topic = vote.get("topic")
            step.vote_options = vote.get("options", [])
            step.vote_quorum = vote.get("quorum", 3)

        # Parse common fields
        step.depends_on = data.get("depends_on", [])
        if "retry" in data:
            retry = data["retry"]
            step.max_retries = retry.get("max", 0)
            step.retry_delay_seconds = retry.get("delay_seconds", 30)
        step.outputs = data.get("outputs", {})

        return step


@dataclass
class Workflow:
    """A complete workflow definition."""

    id: str
    name: str
    description: Optional[str] = None
    version: str = "1.0"

    # Workflow configuration
    steps: list[WorkflowStep] = field(default_factory=list)
    variables: dict[str, Any] = field(default_factory=dict)  # Initial variables

    # Execution settings
    timeout_minutes: int = 120
    on_failure: str = "stop"  # "stop", "continue", "rollback"
    max_concurrent_steps: int = 5

    # Metadata
    created_at: Optional[datetime] = None
    updated_at: Optional[datetime] = None
    created_by: Optional[str] = None

    # Execution state
    status: str = "draft"  # WorkflowStatus value
    started_at: Optional[datetime] = None
    completed_at: Optional[datetime] = None
    current_step_id: Optional[str] = None
    error: Optional[str] = None

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary for JSON serialization."""
        return {
            "id": self.id,
            "name": self.name,
            "description": self.description,
            "version": self.version,
            "steps": [s.to_dict() for s in self.steps],
            "variables": self.variables,
            "settings": {
                "timeout_minutes": self.timeout_minutes,
                "on_failure": self.on_failure,
                "max_concurrent_steps": self.max_concurrent_steps,
            },
            "metadata": {
                "created_by": self.created_by,
            },
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "Workflow":
        """Create from dictionary (parsed JSON/YAML)."""
        settings = data.get("settings", {})
        metadata = data.get("metadata", {})

        workflow = cls(
            id=data["id"],
            name=data["name"],
            description=data.get("description"),
            version=data.get("version", "1.0"),
            steps=[WorkflowStep.from_dict(s) for s in data.get("steps", [])],
            variables=data.get("variables", {}),
            timeout_minutes=settings.get("timeout_minutes", 120),
            on_failure=settings.get("on_failure", "stop"),
            max_concurrent_steps=settings.get("max_concurrent_steps", 5),
            created_by=metadata.get("created_by"),
        )

        return workflow

    @classmethod
    def from_json(cls, json_str: str) -> "Workflow":
        """Create from JSON string."""
        data = json.loads(json_str)
        return cls.from_dict(data)

    def to_json(self, indent: int = 2) -> str:
        """Convert to JSON string."""
        return json.dumps(self.to_dict(), indent=indent)

    def get_step(self, step_id: str) -> Optional[WorkflowStep]:
        """Get a step by ID."""
        for step in self.steps:
            if step.id == step_id:
                return step
            # Check parallel steps
            if step.step_type == "parallel":
                for ps in step.parallel_steps:
                    if ps.id == step_id:
                        return ps
        return None

    def get_ready_steps(self) -> list[WorkflowStep]:
        """Get steps that are ready to execute (dependencies met)."""
        completed_ids = {
            s.id for s in self.steps
            if s.status in ("completed", "skipped")
        }

        ready = []
        for step in self.steps:
            if step.status != "pending":
                continue
            # Check dependencies
            deps_met = all(dep in completed_ids for dep in step.depends_on)
            if deps_met:
                ready.append(step)

        return ready

    def is_complete(self) -> bool:
        """Check if all steps are complete."""
        return all(
            s.status in ("completed", "skipped", "failed")
            for s in self.steps
        )

    def has_failures(self) -> bool:
        """Check if any step failed."""
        return any(s.status == "failed" for s in self.steps)


def _get_nested_value(data: dict[str, Any], path: str) -> Any:
    """Get a nested value from a dict using dot notation."""
    parts = path.split(".")
    current = data
    for part in parts:
        if isinstance(current, dict):
            current = current.get(part)
        else:
            return None
        if current is None:
            return None
    return current
