"""
JSON state machine runner for workflow templates.
"""
from __future__ import annotations

import json
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional

try:
    from backend.models.agent import RunEvent, RunEventType, TurnContext, WorkflowRun, RunStatus
    from backend.services.tool_registry import ToolRegistry
except ImportError:
    from models.agent import RunEvent, RunEventType, TurnContext, WorkflowRun, RunStatus
    from services.tool_registry import ToolRegistry


class StateMachineRunner:
    def __init__(
        self,
        definition: Dict[str, Any],
        registry: ToolRegistry,
        *,
        on_event: Optional[Callable[[RunEvent], None]] = None,
    ) -> None:
        self.definition = definition
        self.registry = registry
        self.on_event = on_event
        self.states: Dict[str, Dict[str, Any]] = definition.get("states", {})

    @classmethod
    def from_file(cls, path: Path, registry: ToolRegistry, **kwargs) -> "StateMachineRunner":
        with open(path, "r", encoding="utf-8") as f:
            definition = json.load(f)
        return cls(definition, registry, **kwargs)

    def _emit(self, run: WorkflowRun, event_type: RunEventType, **payload: Any) -> RunEvent:
        event = RunEvent(
            run_id=run.id,
            type=event_type,
            state=run.current_state,
            tool=payload.pop("tool", None),
            payload=payload,
        )
        if self.on_event:
            self.on_event(event)
        return event

    async def run(self, run: WorkflowRun, ctx: TurnContext) -> List[RunEvent]:
        events: List[RunEvent] = []
        original_on_event = self.on_event

        def collect(event: RunEvent) -> None:
            events.append(event)
            if original_on_event:
                original_on_event(event)

        self.on_event = collect

        initial = self.definition.get("initial")
        if not initial:
            raise ValueError("State machine missing 'initial' state")

        current = initial
        run.status = RunStatus.RUNNING

        try:
            while current:
                state_def = self.states.get(current)
                if not state_def:
                    raise ValueError(f"Unknown state: {current}")

                run.current_state = current
                events.append(self._emit(run, RunEventType.STATE_ENTER, state=current))

                tool_name = state_def.get("tool")
                if tool_name:
                    events.append(self._emit(run, RunEventType.TOOL_START, tool=tool_name))
                    await self.registry.execute(tool_name, ctx)
                    events.append(
                        self._emit(
                            run,
                            RunEventType.TOOL_END,
                            tool=tool_name,
                            success=True,
                        )
                    )

                current = state_def.get("next")
                if current == "end" or current is None:
                    break

            run.status = RunStatus.COMPLETED
            run.result_content = ctx.generated_content
            events.append(self._emit(run, RunEventType.COMPLETE))
        except Exception as exc:
            run.status = RunStatus.FAILED
            run.error = str(exc)
            ctx.error = str(exc)
            events.append(self._emit(run, RunEventType.ERROR, message=str(exc)))
            raise
        finally:
            self.on_event = original_on_event

        return events
