"""ObservabilityMixin — tracing, metrics, telemetry."""
from __future__ import annotations

import inspect
from datetime import datetime, UTC
from typing import Any
from uuid import uuid4

from logos_adk.observe import Metric, Span
from logos_adk.version import API_VERSION, __version__


class ObservabilityMixin:
    """Mixin providing observability methods for Agent."""

    async def _invoke_callback(self, callback: Any, payload: Any) -> None:
        """Invoke a sync or async callback if provided."""
        if callback is None:
            return

        result = callback(payload)
        if inspect.isawaitable(result):
            await result

    def _telemetry_attributes(self, attributes: dict[str, Any] | None = None) -> dict[str, Any]:
        merged = {
            "framework.version": __version__,
            "framework.api_version": API_VERSION,
            "agent.config.path": self._config_path or "inline",
            "agent.config.fingerprint": self._config_fingerprint(),
        }
        if attributes:
            merged.update(attributes)
        return merged

    async def _emit_span(
        self,
        *,
        name: str,
        trace_id: str,
        span_id: str | None = None,
        parent_span_id: str | None,
        started_at: datetime,
        session_id: str | None,
        attributes: dict[str, Any] | None = None,
        status: str = "ok",
    ) -> str:
        """Emit a span to all configured exporters."""
        resolved_span_id = span_id or uuid4().hex
        if not self._config.observers:
            return resolved_span_id

        ended_at = datetime.now(UTC)
        span = Span(
            name=name,
            trace_id=trace_id,
            span_id=resolved_span_id,
            parent_span_id=parent_span_id,
            started_at=started_at,
            ended_at=ended_at,
            duration_ms=(ended_at - started_at).total_seconds() * 1000,
            agent_name=self.name,
            session_id=session_id,
            status=status,
            attributes=self._telemetry_attributes(attributes),
        )
        for exporter in self._config.observers:
            try:
                await exporter.on_span(span)
            except Exception:
                continue
        return resolved_span_id

    async def _flush_observers(self) -> None:
        """Flush configured exporters."""
        for exporter in self._config.observers:
            try:
                await exporter.flush()
            except Exception:
                continue

    async def _emit_metric(
        self,
        *,
        name: str,
        value: float,
        unit: str | None = None,
        attributes: dict[str, Any] | None = None,
    ) -> None:
        """Emit a metric to all configured exporters."""
        if not self._config.observers:
            return
        metric = Metric(
            name=name,
            value=value,
            unit=unit,
            attributes=self._telemetry_attributes(attributes),
        )
        for exporter in self._config.observers:
            try:
                await exporter.on_metric(metric)
            except Exception:
                continue
