"""FunctionRuntime — governed invocation with Chronicle emission."""
from __future__ import annotations

import time
import uuid
from typing import Any

from .base import FunctionContext, FunctionResult
from .registry import FunctionRegistry


class FunctionRuntime:
    def __init__(
        self, registry: FunctionRegistry, chronicle_client: Any = None
    ) -> None:
        self.registry = registry
        self.chronicle_client = chronicle_client

    async def invoke(
        self,
        function_id: str,
        event: dict[str, Any],
        context: FunctionContext,
    ) -> FunctionResult:
        """Direct invocation of a function by ID."""
        function = self.registry.get(function_id)
        if function is None:
            return FunctionResult(success=False, error=f"Unknown function: {function_id}")

        if function.gsap_required and not context.gsap_context_id:
            return FunctionResult(success=False, error="GSAP context required but not provided")

        if not context.invocation_id:
            context.invocation_id = str(uuid.uuid4())

        # Emit FUNCTION_INVOKED (0x2A01) before execution
        await self._emit_chronicle(
            "FUNCTION_INVOKED",
            {
                "event_code": "0x2A01",
                "function_id": function_id,
                "trigger_event_kind": context.trigger_event_kind,
                "trigger_event_cid": context.trigger_event_cid,
                "chronicle_session_id": context.chronicle_session_id,
                "gsap_context_id": context.gsap_context_id,
                "invocation_id": context.invocation_id,
                "pipeline_run_id": context.pipeline_run_id,
            },
            function,
        )

        start = time.monotonic()
        result = await function.handle(event, context)
        result.duration_ms = (time.monotonic() - start) * 1000

        # Emit FUNCTION_COMPLETED (0x2A02) after execution
        await self._emit_chronicle(
            "FUNCTION_COMPLETED",
            {
                "event_code": "0x2A02",
                "function_id": function_id,
                "invocation_id": context.invocation_id,
                "success": result.success,
                "duration_ms": result.duration_ms,
                "output_cid": result.output_cid() if result.data is not None else "",
                "error": result.error,
            },
            function,
        )

        return result

    async def dispatch(
        self,
        event_kind: str,
        event_data: dict[str, Any],
        trigger_cid: str = "",
    ) -> list[dict[str, Any]]:
        """Route an event to all functions registered for this event kind."""
        functions = self.registry.by_trigger(event_kind)
        results = []
        for function in functions:
            context = FunctionContext(
                trigger_event_kind=event_kind,
                trigger_event_cid=trigger_cid,
                invocation_id=str(uuid.uuid4()),
            )
            result = await self.invoke(function.function_id, event_data, context)
            results.append({
                "function_id": function.function_id,
                "success": result.success,
                "data": result.data,
                "error": result.error,
                "duration_ms": result.duration_ms,
                "lineage_cid": result.lineage_cid,
            })
        return results

    async def _emit_chronicle(
        self, event_type: str, payload: dict[str, Any], function: Any
    ) -> None:
        if function.chronicle_enabled and self.chronicle_client is not None:
            try:
                await self.chronicle_client.emit(event_type, payload)
            except Exception:
                pass  # Chronicle failure must not break invocation
