
from typing import Callable, List, Dict, Any, Optional
from dataclasses import dataclass, field
from enum import Enum
import inspect
import copy

class StepStatus(str, Enum):
    pending = "pending"
    running = "running"
    done = "done"
    failed = "failed"

@dataclass
class Step:
    name: str
    func: Callable
    params: Dict[str, Any] = field(default_factory=dict)  # параметры из шаблона
    status: StepStatus = StepStatus.pending
    result: dict = field(default_factory=dict)

@dataclass
class Scenario:
    steps: List[Step]
    current: int = 0
    context: Dict[str, Any] = field(default_factory=dict)  # общий контекст (слияние результатов шагов)

    def _resolve_params(self, step: Step) -> Dict[str, Any]:
        """
        Готовим аргументы для вызова функции шага:
        - Берём копию глобального context (слияние результатов всех прошлых шагов)
        - Поверх накладываем step.params из шаблона (они имеют приоритет)
        - Фильтруем по сигнатуре вызываемой функции (никаких лишних kwargs)
        """
        base = copy.deepcopy(self.context)
        base.update(step.params or {})
        sig = inspect.signature(step.func)
        allowed = {}
        for k, v in base.items():
            if k in sig.parameters or any(p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values()):
                allowed[k] = v
        return allowed

    def run_next(self) -> Step:
        if self.current >= len(self.steps):
            raise IndexError("No more steps to run")
        step = self.steps[self.current]
        step.status = StepStatus.running
        try:
            kwargs = self._resolve_params(step)
            step.result = step.func(**kwargs)
            # обновляем общий контекст результатом шага (поверх)
            if isinstance(step.result, dict):
                self.context.update(step.result)
            step.status = StepStatus.done
        except Exception as e:
            step.status = StepStatus.failed
            step.result = {"error": str(e)}
        self.current += 1
        return step

    def run_all(self):
        while self.current < len(self.steps):
            self.run_next()
