from __future__ import annotations

import contextvars
import os
from contextlib import contextmanager

from src.langfuse_client import build_langfuse_client
from src.tools.helpers import _get, _env_int, _env_float, _parse_dt, _call_with_retry

# ---------------------------------------------------------------------------
# Per-request Langfuse settings override (multi-project support)
# ---------------------------------------------------------------------------
_LANGFUSE_SETTINGS_OVERRIDE: contextvars.ContextVar[dict | None] = contextvars.ContextVar(
    "_LANGFUSE_SETTINGS_OVERRIDE", default=None
)


def set_langfuse_settings_override(settings: dict | None) -> None:
    _LANGFUSE_SETTINGS_OVERRIDE.set(settings)


@contextmanager
def langfuse_settings_scope(settings: dict | None):
    """Context manager that sets a Langfuse settings override and clears it on exit."""
    set_langfuse_settings_override(settings)
    try:
        yield
    finally:
        set_langfuse_settings_override(None)


def _langfuse_config(profile: dict) -> dict:
    override = _LANGFUSE_SETTINGS_OVERRIDE.get(None)
    if override:
        return {
            "host": str(override.get("host") or override.get("base_url", "")).strip(),
            "public_key": str(override.get("public_key", "")).strip(),
            "secret_key": str(override.get("secret_key", "")).strip(),
            "environment": str(override.get("environment", "")).strip(),
        }

    nested = profile.get("langfuse", {}) if isinstance(profile.get("langfuse"), dict) else {}
    host = (
        os.getenv("LANGFUSE_BASE_URL", "").strip()
        or os.getenv("AGENT_LANGFUSE_HOST", "").strip()
        or str(nested.get("host", "")).strip()
    )
    public_key = (
        os.getenv("LANGFUSE_PUBLIC_KEY", "").strip()
        or os.getenv("AGENT_LANGFUSE_PUBLIC_KEY", "").strip()
        or str(nested.get("public_key", "")).strip()
    )
    secret_key = (
        os.getenv("LANGFUSE_SECRET_KEY", "").strip()
        or os.getenv("AGENT_LANGFUSE_SECRET_KEY", "").strip()
        or str(nested.get("secret_key", "")).strip()
    )
    environment = (
        os.getenv("LANGFUSE_ENVIRONMENT", "").strip()
        or os.getenv("AGENT_LANGFUSE_ENVIRONMENT", "").strip()
        or str(nested.get("environment", "")).strip()
    )
    return {
        "host": host,
        "public_key": public_key,
        "secret_key": secret_key,
        "environment": environment,
    }


def _build_langfuse_client(profile: dict):
    cfg = _langfuse_config(profile)
    if not cfg["host"] or not cfg["public_key"] or not cfg["secret_key"]:
        return None, cfg, (
            "Langfuse 설정이 부족합니다. LANGFUSE_BASE_URL/LANGFUSE_PUBLIC_KEY/"
            "LANGFUSE_SECRET_KEY를 환경변수로 설정하세요."
        )
    timeout_sec = _env_float("AGENT_LANGFUSE_HTTP_TIMEOUT_SEC", default=30.0, minimum=3.0, maximum=120.0)
    client, backend, build_error = build_langfuse_client(
        public_key=cfg["public_key"],
        secret_key=cfg["secret_key"],
        host=cfg["host"],
        environment=cfg["environment"],
        timeout_sec=timeout_sec,
    )
    if client is None:
        return None, cfg, f"Langfuse client 생성 실패: {build_error}"
    cfg["backend"] = backend
    return client, cfg, None


def _metadata_match(meta, required: dict[str, str]) -> bool:
    if not required:
        return True
    if not isinstance(meta, dict):
        return False
    for key, expected in required.items():
        current = meta
        for segment in key.split("."):
            if isinstance(current, dict) and segment in current:
                current = current[segment]
            else:
                return False
        if str(current) != str(expected):
            return False
    return True


def _langfuse_call(label: str, fn):
    retries = _env_int("AGENT_LANGFUSE_MAX_RETRIES", default=3, minimum=1, maximum=8)
    base_delay = _env_float("AGENT_LANGFUSE_RETRY_BASE_SEC", default=0.6, minimum=0.1, maximum=5.0)
    return _call_with_retry(label, fn, retries, base_delay)


def _fetch_trace_detail(client, trace_id: str):
    return _langfuse_call(f"langfuse.trace.get({trace_id})", lambda: client.api.trace.get(trace_id))


def _fetch_trace_observations(client, trace_id: str, limit: int = 200) -> list:
    def _call():
        return client.api.observations.get_many(trace_id=trace_id, limit=limit)

    response = _langfuse_call(f"langfuse.observations.get_many({trace_id})", _call)
    return list(getattr(response, "data", []) or [])


def _fetch_traces(client, filters: dict):
    target_limit = max(1, min(int(filters.get("limit", 20) or 20), 100))
    request_limit = max(20, target_limit)
    request_limit = min(request_limit, 100)

    base_kwargs = {"limit": request_limit}
    for key in ("name", "user_id", "session_id", "environment"):
        value = filters.get(key)
        if value:
            base_kwargs[key] = value
    if filters.get("tags"):
        base_kwargs["tags"] = filters["tags"]
    from_ts = _parse_dt(filters.get("from"))
    to_ts = _parse_dt(filters.get("to"))
    if from_ts:
        base_kwargs["from_timestamp"] = from_ts
    if to_ts:
        base_kwargs["to_timestamp"] = to_ts

    need_metadata_filter = bool(filters.get("metadata"))
    max_pages = _env_int("AGENT_LANGFUSE_MAX_PAGES", default=3, minimum=1, maximum=20)
    collected: list = []

    for page in range(1, max_pages + 1):
        kwargs = dict(base_kwargs)
        kwargs["page"] = page
        response = _langfuse_call("langfuse.trace.list", lambda kwargs=kwargs: client.api.trace.list(**kwargs))
        traces = list(getattr(response, "data", []) or [])
        if not traces:
            break

        if need_metadata_filter:
            meta_required = filters.get("metadata", {})
            traces = [trace for trace in traces if _metadata_match(_get(trace, "metadata", {}), meta_required)]

        collected.extend(traces)
        if len(collected) >= target_limit:
            break
        if len(traces) < request_limit:
            break

    traces = collected[:target_limit]

    trace_id = filters.get("trace_id")
    if trace_id:
        detail = _fetch_trace_detail(client, str(trace_id))
        traces = [detail]
    return traces

