"""
core/langfuse_http.py — Client Langfuse HTTP direct, compatible Python 3.14.

Utilise l'API d'ingestion Langfuse (/api/public/ingestion) directement via httpx.
Chaque appel est envoyé dans un thread daemon (non-bloquant).

Variables d'environnement requises (chargées via .env) :
  LANGFUSE_HOST        — ex. http://localhost:3000
  LANGFUSE_PUBLIC_KEY  — ex. pk-lf-formation
  LANGFUSE_SECRET_KEY  — ex. sk-lf-formation
"""

from __future__ import annotations

import base64
import logging
import os
import threading
import uuid
from contextvars import ContextVar
from datetime import datetime, timezone
from typing import Any

import httpx

logger = logging.getLogger("langfuse_http")

# ── Contexte courant ────────────────────────────────────────────────────────────
current_trace_id: ContextVar[str | None] = ContextVar("lf_trace_id", default=None)
current_span_id:  ContextVar[str | None] = ContextVar("lf_span_id",  default=None)


# ── Helpers internes ────────────────────────────────────────────────────────────

def timestamp() -> str:
    return datetime.now(timezone.utc).isoformat()


def _new_id() -> str:
    return str(uuid.uuid4())


def _cfg() -> tuple[str, str, str]:
    host = os.getenv("LANGFUSE_HOST", "http://localhost:3000").rstrip("/")
    pk   = os.getenv("LANGFUSE_PUBLIC_KEY", "")
    sk   = os.getenv("LANGFUSE_SECRET_KEY", "")
    return host, pk, sk


def _auth(pk: str, sk: str) -> str:
    return "Basic " + base64.b64encode(f"{pk}:{sk}".encode()).decode()


def _send(batch: list[dict]) -> None:
    host, pk, sk = _cfg()
    if not pk or not sk or not host:
        return
    try:
        resp = httpx.post(
            f"{host}/api/public/ingestion",
            json={"batch": batch},
            headers={"Authorization": _auth(pk, sk), "Content-Type": "application/json"},
            timeout=5.0,
        )
        if resp.status_code not in (200, 201, 207):
            logger.warning("Langfuse ingestion %s: %s", resp.status_code, resp.text[:300])
    except Exception as exc:
        logger.debug("Langfuse HTTP: %s", exc)


def _bg(batch: list[dict]) -> None:
    """Envoie le batch dans un thread daemon (non-bloquant)."""
    threading.Thread(target=_send, args=(batch,), daemon=True, name="lf-ingest").start()


# ── API publique ────────────────────────────────────────────────────────────────

def create_trace(name: str, input: Any = None) -> str:
    """Crée une trace et positionne le ContextVar current_trace_id. Retourne l'ID."""
    tid = _new_id()
    current_trace_id.set(tid)
    current_span_id.set(None)
    _bg([{
        "id": _new_id(),
        "type": "trace-create",
        "timestamp": timestamp(),
        "body": {
            "id": tid,
            "name": name,
            "input": str(input) if input is not None else None,
        },
    }])
    return tid


def update_trace(trace_id: str, output: Any = None, metadata: dict | None = None) -> None:
    """Met à jour la sortie d'une trace."""
    _bg([{
        "id": _new_id(),
        "type": "trace-create",
        "timestamp": timestamp(),
        "body": {
            "id": trace_id,
            "output": str(output)[:2000] if output is not None else None,
            "metadata": metadata or {},
        },
    }])


def create_generation(
    name: str,
    model: str,
    messages: list[dict],
    output: str,
    usage_in: int = 0,
    usage_out: int = 0,
    start: str | None = None,
    end: str | None = None,
) -> None:
    """Enregistre un appel LLM comme observation 'generation'."""
    tid = current_trace_id.get()
    if not tid:
        return
    _bg([{
        "id": _new_id(),
        "type": "generation-create",
        "timestamp": timestamp(),
        "body": {
            "id": _new_id(),
            "traceId": tid,
            "parentObservationId": current_span_id.get(),
            "name": name,
            "model": model,
            "input": messages,
            "output": output,
            "startTime": start or timestamp(),
            "endTime":   end   or timestamp(),
            "usage": {"input": usage_in, "output": usage_out, "unit": "TOKENS"},
        },
    }])


def create_span(
    name: str,
    input: Any = None,
    output: Any = None,
    start: str | None = None,
    end: str | None = None,
) -> None:
    """Enregistre une exécution d'outil comme observation 'span'."""
    tid = current_trace_id.get()
    if not tid:
        return
    _bg([{
        "id": _new_id(),
        "type": "span-create",
        "timestamp": timestamp(),
        "body": {
            "id": _new_id(),
            "traceId": tid,
            "parentObservationId": current_span_id.get(),
            "name": name,
            "input":  str(input)[:500]  if input  is not None else None,
            "output": str(output)[:500] if output is not None else None,
            "startTime": start or timestamp(),
            "endTime":   end   or timestamp(),
        },
    }])
