# SPDX-License-Identifier: MIT
"""Langfuse v4 adapter for the deterministic workflow trace events."""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Dict, Literal, Mapping, Optional, Protocol

from workflow import ObservationKind, TraceEvent, TraceSink

LangfuseLevel = Literal["DEFAULT", "WARNING", "ERROR"]


class LangfuseObservation(Protocol):
    """Minimal observation surface used by the fixture adapter."""

    trace_id: str
    id: str

    def update(
        self,
        *,
        metadata: Mapping[str, object],
        level: LangfuseLevel,
        status_message: Optional[str] = None,
    ) -> "LangfuseObservation":
        """Update the final sanitized metadata and status."""

    def end(self) -> "LangfuseObservation":
        """End the observation."""


class LangfuseClient(Protocol):
    """Minimal Langfuse Python SDK v4 client surface used by the adapter."""

    def start_observation(
        self,
        *,
        name: str,
        as_type: ObservationKind,
        trace_context: Optional[Mapping[str, str]],
        metadata: Mapping[str, object],
        version: str,
        model: Optional[str] = None,
    ) -> LangfuseObservation:
        """Start one observation with optional explicit parentage."""

    def flush(self) -> None:
        """Flush queued observations."""


def _status(event: TraceEvent) -> tuple[LangfuseLevel, Optional[str]]:
    if event.status == "error":
        error_type = dict(event.attributes).get("error_type", "fixture error")
        return "ERROR", f"Fixture observation failed: {error_type}"
    if event.status == "warning":
        return "WARNING", "Fixture requires human review"
    return "DEFAULT", None


def _metadata(event: TraceEvent) -> Dict[str, object]:
    metadata: Dict[str, object] = {
        "fixture": "futurecraft-agent-tracing",
        "logical_observation_id": event.observation_id,
        "logical_parent_id": event.parent_id,
        "attempt": event.attempt,
    }
    metadata.update(dict(event.attributes))
    return metadata


@dataclass
class LangfuseTraceSink(TraceSink):
    """Map sanitized fixture events to explicit Langfuse observations."""

    client: LangfuseClient
    fixture_version: str = "2026-08-26"
    observations: Dict[str, LangfuseObservation] = field(default_factory=dict)

    def record(self, event: TraceEvent) -> None:
        if event.phase == "start":
            self._start(event)
            return
        self._end(event)

    def flush(self) -> None:
        """Flush queued telemetry after the application result is available."""

        self.client.flush()

    def _start(self, event: TraceEvent) -> None:
        if event.observation_id in self.observations:
            raise ValueError(f"duplicate observation: {event.observation_id}")

        trace_context: Optional[Mapping[str, str]] = None
        if event.parent_id:
            parent = self.observations.get(event.parent_id)
            if parent is None:
                raise ValueError(f"unknown parent: {event.parent_id}")
            trace_context = {
                "trace_id": parent.trace_id,
                "parent_span_id": parent.id,
            }

        model = "recorded-fixture-v1" if event.kind == "generation" else None
        observation = self.client.start_observation(
            name=event.name,
            as_type=event.kind,
            trace_context=trace_context,
            metadata=_metadata(event),
            version=self.fixture_version,
            model=model,
        )
        self.observations[event.observation_id] = observation

    def _end(self, event: TraceEvent) -> None:
        observation = self.observations.get(event.observation_id)
        if observation is None:
            raise ValueError(f"unknown observation: {event.observation_id}")

        level, status_message = _status(event)
        observation.update(
            metadata=_metadata(event),
            level=level,
            status_message=status_message,
        ).end()


def create_langfuse_trace_sink() -> LangfuseTraceSink:
    """Create the adapter with the environment-configured Langfuse v4 client."""

    from langfuse import get_client

    return LangfuseTraceSink(client=get_client())
