from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime
import os
import sys
import json
from types import SimpleNamespace
from typing import Any
from urllib.parse import quote, urlencode

import httpx


def _strip_known_api_suffix(host: str) -> str:
    normalized = str(host or "").strip().rstrip("/")
    for suffix in ("/api/public", "/api"):
        if normalized.endswith(suffix):
            return normalized[: -len(suffix)]
    return normalized


def _jsonable(value: Any) -> Any:
    if isinstance(value, datetime):
        return value.isoformat().replace("+00:00", "Z")
    return value


def _compact_params(params: dict[str, Any] | None) -> dict[str, Any]:
    if not params:
        return {}
    cleaned: dict[str, Any] = {}
    for key, raw in params.items():
        if raw is None:
            continue
        if isinstance(raw, (list, tuple)):
            values = [_jsonable(item) for item in raw if item is not None]
            if values:
                cleaned[key] = values
            continue
        cleaned[key] = _jsonable(raw)
    return cleaned


def _as_obj(payload: Any):
    if isinstance(payload, dict):
        return SimpleNamespace(**payload)
    return payload


@dataclass
class _LangfuseHttpTransport:
    host: str
    public_key: str
    secret_key: str
    timeout_sec: float = 30.0

    def _build_url(self, path: str, params: dict[str, Any] | None = None) -> str:
        root = _strip_known_api_suffix(self.host)
        base = root.rstrip("/") + "/"
        relative = path.lstrip("/")
        url = base + relative
        query = _compact_params(params)
        if query:
            url += "?" + urlencode(query, doseq=True)
        return url

    def request_json(
        self,
        *,
        method: str,
        path: str,
        params: dict[str, Any] | None = None,
        body: dict[str, Any] | None = None,
    ) -> Any:
        payload = None
        headers = {
            "Accept": "application/json",
            "User-Agent": "langfuse-lens-agent/http-fallback",
            "x-langfuse-sdk-name": "python",
            "x-langfuse-sdk-version": "fallback",
            "x-langfuse-public-key": self.public_key,
        }
        if body is not None:
            payload = json.dumps(body, ensure_ascii=False).encode("utf-8")
            headers["Content-Type"] = "application/json"
        url = self._build_url(path, params)
        try:
            with httpx.Client(timeout=self.timeout_sec, auth=(self.public_key, self.secret_key), headers=headers) as client:
                res = client.request(method.upper(), url, content=payload)
        except Exception as exc:
            raise RuntimeError(f"Langfuse HTTP 연결 실패: {exc}") from exc

        text = res.text or ""
        if res.status_code >= 400:
            raise RuntimeError(f"Langfuse HTTP {res.status_code}: {text[:1000]}")
        if not text:
            return {}
        try:
            return res.json()
        except Exception:
            return {"raw": text}


class _TraceAPI:
    def __init__(self, transport: _LangfuseHttpTransport, environment: str):
        self._transport = transport
        self._environment = environment

    def list(self, **kwargs):
        params = {
            "page": kwargs.get("page"),
            "limit": kwargs.get("limit"),
            "userId": kwargs.get("user_id"),
            "name": kwargs.get("name"),
            "sessionId": kwargs.get("session_id"),
            "fromTimestamp": kwargs.get("from_timestamp"),
            "toTimestamp": kwargs.get("to_timestamp"),
            "orderBy": kwargs.get("order_by"),
            "tags": kwargs.get("tags"),
            "version": kwargs.get("version"),
            "release": kwargs.get("release"),
            "environment": kwargs.get("environment") or self._environment or None,
            "fields": kwargs.get("fields"),
            "filter": kwargs.get("filter"),
        }
        payload = self._transport.request_json(method="GET", path="api/public/traces", params=params)
        return _as_obj(payload)

    def get(self, trace_id: str):
        encoded = quote(str(trace_id), safe="")
        return self._transport.request_json(method="GET", path=f"api/public/traces/{encoded}")


class _ObservationsAPI:
    def __init__(self, transport: _LangfuseHttpTransport, environment: str):
        self._transport = transport
        self._environment = environment

    def get_many(self, **kwargs):
        params = {
            "page": kwargs.get("page"),
            "limit": kwargs.get("limit"),
            "name": kwargs.get("name"),
            "userId": kwargs.get("user_id"),
            "type": kwargs.get("type"),
            "traceId": kwargs.get("trace_id"),
            "level": kwargs.get("level"),
            "parentObservationId": kwargs.get("parent_observation_id"),
            "environment": kwargs.get("environment") or self._environment or None,
            "fromStartTime": kwargs.get("from_start_time"),
            "toStartTime": kwargs.get("to_start_time"),
            "version": kwargs.get("version"),
            "filter": kwargs.get("filter"),
        }
        payload = self._transport.request_json(method="GET", path="api/public/observations", params=params)
        return _as_obj(payload)


class _ApiNamespace:
    def __init__(self, transport: _LangfuseHttpTransport, environment: str):
        self.trace = _TraceAPI(transport, environment)
        self.observations = _ObservationsAPI(transport, environment)


class LangfuseHTTPClient:
    def __init__(
        self,
        *,
        public_key: str,
        secret_key: str,
        host: str,
        environment: str = "",
        timeout_sec: float = 30.0,
    ):
        self._transport = _LangfuseHttpTransport(
            host=host,
            public_key=public_key,
            secret_key=secret_key,
            timeout_sec=max(3.0, float(timeout_sec)),
        )
        self._environment = str(environment or "").strip()
        self.api = _ApiNamespace(self._transport, self._environment)

    def get_prompt(self, prompt_name: str, *, label: str | None = None, version: int | None = None, **_kwargs):
        encoded = quote(str(prompt_name), safe="")
        payload = self._transport.request_json(
            method="GET",
            path=f"api/public/v2/prompts/{encoded}",
            params={"label": label, "version": version},
        )
        return _as_obj(payload)

    def create_prompt(
        self,
        *,
        name: str,
        prompt: Any,
        type: str = "text",
        labels: list[str] | None = None,
        tags: list[str] | None = None,
        commit_message: str | None = None,
        config: dict[str, Any] | None = None,
        **_kwargs,
    ):
        body: dict[str, Any] = {
            "name": name,
            "prompt": prompt,
            "type": type,
        }
        if labels:
            body["labels"] = labels
        if tags:
            body["tags"] = tags
        if commit_message:
            body["commitMessage"] = commit_message
        if config is not None:
            body["config"] = config
        payload = self._transport.request_json(method="POST", path="api/public/v2/prompts", body=body)
        return _as_obj(payload)


def _build_sdk_client(*, public_key: str, secret_key: str, host: str, environment: str):
    from langfuse import Langfuse

    kwargs = {
        "public_key": public_key,
        "secret_key": secret_key,
        "environment": environment or None,
    }
    try:
        return Langfuse(host=host, **kwargs)
    except TypeError:
        return Langfuse(base_url=host, **kwargs)


def build_langfuse_client(
    *,
    public_key: str,
    secret_key: str,
    host: str,
    environment: str = "",
    timeout_sec: float = 30.0,
):
    force_sdk = str(os.environ.get("AGENT_LANGFUSE_FORCE_SDK", "")).strip().lower() in {
        "1",
        "true",
        "yes",
        "on",
    }
    sdk_error: str | None = None
    should_try_sdk = force_sdk or sys.version_info < (3, 14)
    if should_try_sdk:
        try:
            client = _build_sdk_client(
                public_key=public_key,
                secret_key=secret_key,
                host=host,
                environment=environment,
            )
            return client, "sdk", None
        except Exception as exc:
            sdk_error = str(exc)
    else:
        sdk_error = "skipped on Python >= 3.14 (known SDK compatibility issue)"

    try:
        client = LangfuseHTTPClient(
            public_key=public_key,
            secret_key=secret_key,
            host=host,
            environment=environment,
            timeout_sec=timeout_sec,
        )
        return client, "http-fallback", None
    except Exception as exc:
        return None, "", f"sdk={sdk_error}; http={exc}"
