"""Langfuse-backed trace emitter."""

from __future__ import annotations

import logging
from typing import Any, Callable

from dr_langfuse.client import (
    LangfuseConfig,
    default_langfuse_client_factory,
    resolve_client,
)
from dr_langfuse.errors.codes import ErrorCode
from dr_langfuse.errors.error_envelope import ErrorEnvelope
from dr_langfuse.tracing.models import TraceAck, TraceEventRequest

_log = logging.getLogger(__name__)


class LangfuseTraceEmitter:
    """Trace emitter implementation backed by Langfuse."""

    def __init__(
        self,
        *,
        config: LangfuseConfig | None = None,
        client: Any | None = None,
        client_factory: Callable[[LangfuseConfig], Any] | None = None,
    ) -> None:
        self._config = config or LangfuseConfig.from_env()
        self._client_factory = client_factory or default_langfuse_client_factory
        self._client_resolution = resolve_client(
            client=client,
            config=self._config,
            client_factory=self._client_factory,
        )

    def emit_trace(self, event: TraceEventRequest) -> TraceAck:
        if self._client_resolution.init_error is not None:
            return TraceAck(accepted=False, error=self._client_resolution.init_error)

        client = self._client_resolution.client
        if client is None:
            init_error = self._client_resolution.init_error
            init_error_details = (
                init_error.model_dump(mode="json") if init_error is not None else None
            )
            return TraceAck(
                accepted=False,
                error=ErrorEnvelope(
                    code=ErrorCode.INTERNAL_ERROR,
                    message=(
                        "Langfuse client resolution failed before emit_trace; "
                        "init_error details attached"
                    ),
                    retriable=False,
                    details={
                        "operation": "emit_trace",
                        "reason": "client_resolution_failed",
                        "init_error": init_error_details,
                    },
                ),
            )

        try:
            client.create_event(**_create_event_request_kwargs(event))
        except Exception as exc:
            return TraceAck(
                accepted=False,
                error=ErrorEnvelope(exception=exc, operation="emit_trace"),
            )

        trace_id: str | None = None
        get_trace_id = getattr(client, "get_current_trace_id", None)
        if callable(get_trace_id):
            try:
                maybe_trace_id = get_trace_id()
                if isinstance(maybe_trace_id, str):
                    trace_id = maybe_trace_id
            except Exception:
                _log.debug("get_current_trace_id failed on %r", client, exc_info=True)

        flush = getattr(client, "flush", None)
        if callable(flush):
            try:
                flush()
            except Exception:
                _log.debug("flush failed on %r", client, exc_info=True)

        if trace_id is None:
            return TraceAck(
                accepted=False,
                error=ErrorEnvelope(
                    code=ErrorCode.INTERNAL_ERROR,
                    message=(
                        "Langfuse event submitted but trace_id could not be resolved"
                    ),
                    retriable=False,
                    details={"operation": "emit_trace", "reason": "missing_trace_id"},
                ),
            )

        return TraceAck(accepted=True, trace_id=trace_id)


def _create_event_request_kwargs(event: TraceEventRequest) -> dict[str, object]:
    kwargs: dict[str, object] = {"name": event.event_name}
    if event.metadata:
        kwargs["metadata"] = event.metadata

    input_payload: dict[str, object] = {}
    if event.tags:
        input_payload["tags"] = event.tags
    if event.session_id is not None:
        input_payload["session_id"] = event.session_id
    if input_payload:
        kwargs["input"] = input_payload
    return kwargs
