"""Span attribute names for Mingx OpenTelemetry integration."""

import json
from typing import Any, Dict, Optional, Union

from mingx._client.constants import ObservationType, normalize_observation_type


def _is_message_like(obj: Any) -> bool:
    """Duck-type: LangChain BaseMessage 等（含 content 且 type/role 为字符串）。"""
    if obj is None or isinstance(obj, (dict, list, str, int, float, bool)):
        return False
    if not hasattr(obj, "content"):
        return False
    t, r = getattr(obj, "type", None), getattr(obj, "role", None)
    return isinstance(t, str) or isinstance(r, str)


def _message_like_to_dict(obj: Any) -> Dict[str, Any]:
    """将 message 型对象转为标准结构 {role, content, ...}，便于 JSON 且可读。"""
    content = getattr(obj, "content", "")
    role = getattr(obj, "type", None) or getattr(obj, "role", "message")
    out: Dict[str, Any] = {"role": role, "content": content}
    if getattr(obj, "tool_calls", None):
        out["tool_calls"] = obj.tool_calls
    if getattr(obj, "tool_call_id", None) is not None:
        out["tool_call_id"] = obj.tool_call_id
    ak = getattr(obj, "additional_kwargs", None)
    if isinstance(ak, dict) and ak.get("name") is not None:
        out["name"] = ak["name"]
    return out


def _to_json_safe(obj: Any) -> Any:
    """递归转为可 JSON 序列化的结构。
    - 基本类型：原样返回
    - message 型：{role, content, ...}
    - 含 .messages 的（如 ChatPromptValue）：{messages: [...]}
    - list/dict：递归
    - 普通 class 实例：优先 __dict__ 或 model_dump() 转成 dict 再递归，否则 str(obj)
    """
    if obj is None or isinstance(obj, (str, int, float, bool)):
        return obj
    if _is_message_like(obj):
        return _message_like_to_dict(obj)
    # ChatPromptValue 等：含 .messages 的列表，转为 {messages: [{role, content}, ...]}
    if hasattr(obj, "messages") and isinstance(getattr(obj, "messages", None), list):
        return {"messages": [_to_json_safe(m) for m in obj.messages]}
    if isinstance(obj, list):
        return [_to_json_safe(i) for i in obj]
    if isinstance(obj, dict):
        return {k: _to_json_safe(v) for k, v in obj.items()}
    # 普通 class 实例：用 __dict__ 或 Pydantic model_dump() 转成 dict 再递归
    if isinstance(obj, type):
        return str(obj)  # 传入的是 class 本身，不展开
    try:
        if hasattr(obj, "model_dump") and callable(getattr(obj, "model_dump")):
            return _to_json_safe(obj.model_dump())
        if hasattr(obj, "dict") and callable(getattr(obj, "dict")):
            return _to_json_safe(obj.dict())
        if hasattr(obj, "__dict__") and isinstance(getattr(obj, "__dict__"), dict):
            return {k: _to_json_safe(v) for k, v in obj.__dict__.items()}
    except (TypeError, ValueError, RecursionError):
        pass
    try:
        return json.loads(json.dumps(obj, default=str))
    except (TypeError, ValueError):
        return str(obj)


def _serialize(obj: Any) -> Optional[str]:
    """统一序列化：message 型转标准结构，其余转 JSON；保证简洁可读。"""
    if obj is None:
        return None
    if isinstance(obj, str):
        return obj
    try:
        safe = _to_json_safe(obj)
        if isinstance(safe, str):
            return safe
        return json.dumps(safe, ensure_ascii=False, default=str)
    except (TypeError, ValueError):
        return str(obj)


def serialize_for_attribute(obj: Any) -> Optional[str]:
    """将任意值序列化为 OTEL 属性可用的字符串（观测/跟踪的 input、output 等均经此序列化）。
    message 型会转为标准 {role, content, ...} 再输出 JSON，其它为标准 JSON，保证简洁可读。
    """
    return _serialize(obj)


class MingxOtelSpanAttributes:
    """Attribute key constants for Mingx span attributes (mingx.* namespace)."""

    # Trace-level attributes (set on root span or via update_current_trace).
    # Trace I/O 不单独设：用 root span 的 mingx.observation.input / output 表示整条 trace 的输入输出。
    TRACE_NAME = "mingx.trace.name"
    TRACE_USER_ID = "mingx.trace.user_id"
    TRACE_SESSION_ID = "mingx.trace.session_id"
    TRACE_METADATA = "mingx.trace.metadata"
    TRACE_TAGS = "mingx.trace.tags"

    # Observation-level attributes
    OBSERVATION_TYPE = "mingx.observation.type"
    OBSERVATION_METADATA = "mingx.observation.metadata"
    OBSERVATION_LEVEL = "mingx.observation.level"
    OBSERVATION_STATUS_MESSAGE = "mingx.observation.status_message"
    OBSERVATION_INPUT = "mingx.observation.input"
    OBSERVATION_OUTPUT = "mingx.observation.output"

    # Generation-specific attributes
    OBSERVATION_MODEL = "mingx.observation.model"
    OBSERVATION_MODEL_PARAMETERS = "mingx.observation.model_parameters"
    OBSERVATION_USAGE_DETAILS = "mingx.observation.usage_details"
    OBSERVATION_COST_DETAILS = "mingx.observation.cost_details"
    OBSERVATION_COMPLETION_START_TIME = "mingx.observation.completion_start_time"

    # General
    ENVIRONMENT = "mingx.environment"
    VERSION = "mingx.version"


def create_trace_attributes(
    *,
    name: Optional[str] = None,
    user_id: Optional[str] = None,
    session_id: Optional[str] = None,
    version: Optional[str] = None,
    metadata: Optional[Any] = None,
    tags: Optional[list[str]] = None,
) -> dict[str, Any]:
    """Build trace-level attribute dict; omit None values. Trace I/O 使用 root span 的 observation.input/output。"""
    attrs: dict[str, Any] = {
        MingxOtelSpanAttributes.TRACE_NAME: name,
        MingxOtelSpanAttributes.TRACE_USER_ID: user_id,
        MingxOtelSpanAttributes.TRACE_SESSION_ID: session_id,
        MingxOtelSpanAttributes.VERSION: version,
        MingxOtelSpanAttributes.TRACE_TAGS: _serialize(tags),
    }
    if metadata is not None:
        if isinstance(metadata, dict):
            for k, v in metadata.items():
                key = f"{MingxOtelSpanAttributes.TRACE_METADATA}.{k}"
                attrs[key] = v if isinstance(v, (str, int, float, bool)) else _serialize(v)
        else:
            attrs[MingxOtelSpanAttributes.TRACE_METADATA] = _serialize(metadata)
    return {k: v for k, v in attrs.items() if v is not None}


def create_span_attributes(
    *,
    observation_type: Union[ObservationType, str] = ObservationType.SPAN,
    metadata: Optional[Any] = None,
    input: Optional[Any] = None,
    output: Optional[Any] = None,
    level: Optional[str] = None,
    status_message: Optional[str] = None,
    version: Optional[str] = None,
) -> dict[str, Any]:
    """Build observation-level attribute dict for span/event."""
    type_str = normalize_observation_type(observation_type)
    attrs: dict[str, Any] = {
        MingxOtelSpanAttributes.OBSERVATION_TYPE: type_str,
        MingxOtelSpanAttributes.OBSERVATION_LEVEL: level,
        MingxOtelSpanAttributes.OBSERVATION_STATUS_MESSAGE: status_message,
        MingxOtelSpanAttributes.VERSION: version,
        MingxOtelSpanAttributes.OBSERVATION_INPUT: _serialize(input),
        MingxOtelSpanAttributes.OBSERVATION_OUTPUT: _serialize(output),
    }
    if metadata is not None:
        if isinstance(metadata, dict):
            for k, v in metadata.items():
                key = f"{MingxOtelSpanAttributes.OBSERVATION_METADATA}.{k}"
                attrs[key] = v if isinstance(v, (str, int, float, bool)) else _serialize(v)
        else:
            attrs[MingxOtelSpanAttributes.OBSERVATION_METADATA] = _serialize(metadata)
    return {k: v for k, v in attrs.items() if v is not None}


def create_generation_attributes(
    *,
    metadata: Optional[Any] = None,
    input: Optional[Any] = None,
    output: Optional[Any] = None,
    level: Optional[str] = None,
    status_message: Optional[str] = None,
    version: Optional[str] = None,
    model: Optional[str] = None,
    model_parameters: Optional[dict[str, Any]] = None,
    usage_details: Optional[dict[str, int]] = None,
    cost_details: Optional[dict[str, float]] = None,
    completion_start_time: Optional[str] = None,
) -> dict[str, Any]:
    """Build observation-level attribute dict for generation."""
    base = create_span_attributes(
        observation_type=ObservationType.GENERATION,
        metadata=metadata,
        input=input,
        output=output,
        level=level,
        status_message=status_message,
        version=version,
    )
    extra = {
        MingxOtelSpanAttributes.OBSERVATION_MODEL: model,
        MingxOtelSpanAttributes.OBSERVATION_MODEL_PARAMETERS: _serialize(model_parameters),
        MingxOtelSpanAttributes.OBSERVATION_USAGE_DETAILS: _serialize(usage_details),
        MingxOtelSpanAttributes.OBSERVATION_COST_DETAILS: _serialize(cost_details),
        MingxOtelSpanAttributes.OBSERVATION_COMPLETION_START_TIME: completion_start_time,
    }
    base.update({k: v for k, v in extra.items() if v is not None})
    return base
