"""
Keys follow the OpenTelemetry GenAI semantic conventions (experimental). Anything
without a semconv equivalent lives under the ``litellm.*`` vendor namespace.
"""

from enum import Enum
from typing import Final


class GenAIOperation(str, Enum):
    """Values for ``gen_ai.operation.name``."""

    CHAT = "chat"
    TEXT_COMPLETION = "text_completion"
    EMBEDDINGS = "embeddings"
    GENERATE_CONTENT = "generate_content"
    CREATE_AGENT = "create_agent"  # reserved for future agent spans
    INVOKE_AGENT = "invoke_agent"  # reserved for future agent spans
    EXECUTE_TOOL = "execute_tool"  # MCP tool-call spans


class GenAIProvider(str, Enum):
    """Common values for the ``gen_ai.provider.name`` attribute."""

    OPENAI = "openai"
    ANTHROPIC = "anthropic"
    AWS_BEDROCK = "aws.bedrock"
    AZURE_AI_OPENAI = "azure.ai.openai"
    AZURE_AI_INFERENCE = "azure.ai.inference"
    GCP_GEMINI = "gcp.gemini"
    GCP_VERTEX_AI = "gcp.vertex_ai"
    COHERE = "cohere"
    MISTRAL_AI = "mistral_ai"
    DEEPSEEK = "deepseek"
    GROQ = "groq"
    PERPLEXITY = "perplexity"
    X_AI = "x_ai"
    IBM_WATSONX_AI = "ibm.watsonx.ai"


class MCPMethod(str, Enum):
    """Well-known values for ``mcp.method.name`` that litellm's MCP gateway
    serves. The value is the JSON-RPC method exactly as it travels on the wire."""

    TOOLS_CALL = "tools/call"
    TOOLS_LIST = "tools/list"
    PROMPTS_GET = "prompts/get"
    PROMPTS_LIST = "prompts/list"


class GenAI:
    """Canonical OTel GenAI span-attribute keys."""

    # request
    OPERATION_NAME: Final = "gen_ai.operation.name"
    PROVIDER_NAME: Final = "gen_ai.provider.name"
    REQUEST_MODEL: Final = "gen_ai.request.model"
    REQUEST_TEMPERATURE: Final = "gen_ai.request.temperature"
    REQUEST_TOP_P: Final = "gen_ai.request.top_p"
    REQUEST_TOP_K: Final = "gen_ai.request.top_k"
    REQUEST_MAX_TOKENS: Final = "gen_ai.request.max_tokens"
    REQUEST_FREQUENCY_PENALTY: Final = "gen_ai.request.frequency_penalty"
    REQUEST_PRESENCE_PENALTY: Final = "gen_ai.request.presence_penalty"
    REQUEST_STOP_SEQUENCES: Final = "gen_ai.request.stop_sequences"
    REQUEST_SEED: Final = "gen_ai.request.seed"
    REQUEST_CHOICE_COUNT: Final = "gen_ai.request.choice.count"
    REQUEST_ENCODING_FORMATS: Final = "gen_ai.request.encoding_formats"
    # response
    RESPONSE_ID: Final = "gen_ai.response.id"
    RESPONSE_MODEL: Final = "gen_ai.response.model"
    RESPONSE_FINISH_REASONS: Final = "gen_ai.response.finish_reasons"
    # usage
    USAGE_INPUT_TOKENS: Final = "gen_ai.usage.input_tokens"
    USAGE_OUTPUT_TOKENS: Final = "gen_ai.usage.output_tokens"
    # content (opt-in, gated by capture mode)
    INPUT_MESSAGES: Final = "gen_ai.input.messages"
    OUTPUT_MESSAGES: Final = "gen_ai.output.messages"
    SYSTEM_INSTRUCTIONS: Final = "gen_ai.system_instructions"
    OUTPUT_TYPE: Final = "gen_ai.output.type"
    CONVERSATION_ID: Final = "gen_ai.conversation.id"
    # agent (reserved)
    AGENT_ID: Final = "gen_ai.agent.id"
    AGENT_NAME: Final = "gen_ai.agent.name"
    # tool / tool-call (stamped on MCP tool-call spans). Arguments and result are
    # the tool's input/output payloads — sensitive, so they're opt-in and gated by
    # the same content-capture mode as prompt/response content.
    TOOL_NAME: Final = "gen_ai.tool.name"
    TOOL_CALL_ID: Final = "gen_ai.tool.call.id"
    TOOL_CALL_ARGUMENTS: Final = "gen_ai.tool.call.arguments"
    TOOL_CALL_RESULT: Final = "gen_ai.tool.call.result"
    # prompt (MCP ``prompts/get`` etc.)
    PROMPT_NAME: Final = "gen_ai.prompt.name"


class MCP:
    """OTel GenAI MCP (Model Context Protocol) span-attribute keys.

    ``METHOD_NAME`` is the only key litellm populates from a closed request today;
    the rest are part of the convention's vocabulary and are stamped when the
    corresponding signal (session, protocol version, resource) is available.
    """

    METHOD_NAME: Final = "mcp.method.name"
    SESSION_ID: Final = "mcp.session.id"
    PROTOCOL_VERSION: Final = "mcp.protocol.version"
    RESOURCE_URI: Final = "mcp.resource.uri"


class JsonRpc:
    """JSON-RPC keys carried on MCP spans. The error/status code lives in the
    ``rpc.*`` namespace per semconv, not ``jsonrpc.*``."""

    REQUEST_ID: Final = "jsonrpc.request.id"
    PROTOCOL_VERSION: Final = "jsonrpc.protocol.version"
    RESPONSE_STATUS_CODE: Final = "rpc.response.status_code"


class NetworkTransport(str, Enum):
    """Well-known values for ``network.transport``."""

    TCP = "tcp"
    UDP = "udp"
    QUIC = "quic"
    UNIX = "unix"
    PIPE = "pipe"


class Network:
    """OTel network keys, recommended on MCP spans to describe the transport
    carrying the JSON-RPC messages (stdio pipe, HTTP, websocket, …)."""

    PROTOCOL_NAME: Final = "network.protocol.name"
    PROTOCOL_VERSION: Final = "network.protocol.version"
    TRANSPORT: Final = "network.transport"


class Client:
    """Peer (client) network keys, stamped on MCP *server* spans the same way
    ``server.*`` is stamped on client spans."""

    ADDRESS: Final = "client.address"
    PORT: Final = "client.port"


class Error:
    TYPE: Final = "error.type"


class ExceptionEvent:
    """OTel exception-event name and attribute keys (semconv ``exception.*``).

    The full error message rides ``exception.message`` on a span event rather than
    a custom string attribute. Backends recognise these semantic-convention names
    and map them as full text; an unrecognised key (e.g. ``error_message``) falls
    into the default dynamic template, which truncates strings to a 1024-char
    ``keyword``.
    """

    NAME: Final = "exception"
    TYPE: Final = "exception.type"
    MESSAGE: Final = "exception.message"


class Server:
    ADDRESS: Final = "server.address"
    PORT: Final = "server.port"


class DB:
    """Database / cache client-span keys (OTel ``db.*`` semconv).

    Stamped on ``DB_CALL`` spans (redis / postgres), which are CLIENT spans for
    outbound datastore calls — not on the INTERNAL ``SERVICE`` spans.
    """

    SYSTEM_NAME: Final = "db.system.name"
    OPERATION_NAME: Final = "db.operation.name"


class HTTP:
    """HTTP server-span keys. Belong on the SERVER span only (never promoted)."""

    REQUEST_METHOD: Final = "http.request.method"
    ROUTE: Final = "http.route"
    RESPONSE_STATUS_CODE: Final = "http.response.status_code"
    URL_PATH: Final = "url.path"


class LiteLLM:
    """Vendor-extension keys (no semconv equivalent). Always ``litellm.*``."""

    CALL_ID: Final = "litellm.call_id"
    COST_PREFIX: Final = "litellm.cost."
    METADATA_PREFIX: Final = "litellm.metadata."
    TEAM_ID: Final = "litellm.team.id"
    TEAM_ALIAS: Final = "litellm.team.alias"
    # The team's free-form metadata dict, JSON-serialized into a single value.
    TEAM_METADATA: Final = "litellm.team.metadata"
    KEY_HASH: Final = "litellm.api_key.hash"
    END_USER: Final = "litellm.end_user.id"
    # The model string litellm actually sent to the provider (the deployment's
    # ``litellm_params.model``), distinct from the user-facing ``gen_ai.request.model``.
    PROVIDER_MODEL: Final = "litellm.provider.model"
    REQUEST_STREAMING: Final = "litellm.request.streaming"
    GUARDRAIL_NAME: Final = "litellm.guardrail.name"
    GUARDRAIL_MODE: Final = "litellm.guardrail.mode"
    GUARDRAIL_STATUS: Final = "litellm.guardrail.status"
    GUARDRAIL_PROVIDER: Final = "litellm.guardrail.provider"
    GUARDRAIL_ACTION: Final = "litellm.guardrail.action"
    GUARDRAIL_RESPONSE: Final = "litellm.guardrail.response"
    GUARDRAIL_VIOLATION_CATEGORIES: Final = "litellm.guardrail.violation_categories"
    GUARDRAIL_CONFIDENCE_SCORE: Final = "litellm.guardrail.confidence_score"
    GUARDRAIL_RISK_SCORE: Final = "litellm.guardrail.risk_score"
    GUARDRAIL_MASKED_ENTITY_COUNT: Final = "litellm.guardrail.masked_entity_count"
    GUARDRAIL_DURATION: Final = "litellm.guardrail.duration"
    GUARDRAIL_ID: Final = "litellm.guardrail.id"
    GUARDRAIL_POLICY_TEMPLATE: Final = "litellm.guardrail.policy_template"
    GUARDRAIL_DETECTION_METHOD: Final = "litellm.guardrail.detection_method"
    SERVICE_NAME: Final = "litellm.service.name"
    SERVICE_CALL_TYPE: Final = "litellm.service.call_type"
    PREPROCESSING_MS: Final = "litellm.preprocessing.duration_ms"
    # The logical name of the MCP server a tool call was routed to. There is no
    # semconv key for an MCP server's *name* (the convention uses ``server.address``
    # for its network location), so it lives under the vendor namespace.
    MCP_SERVER_NAME: Final = "litellm.mcp.server.name"


class Metric:
    """GenAI metric instrument names."""

    TOKEN_USAGE: Final = "gen_ai.client.token.usage"
    OPERATION_DURATION: Final = "gen_ai.client.operation.duration"
    TOKEN_COST: Final = "gen_ai.client.token.cost"
    TIME_TO_FIRST_TOKEN: Final = "gen_ai.client.response.time_to_first_token"
    TIME_PER_OUTPUT_TOKEN: Final = "gen_ai.client.response.time_per_output_token"
    RESPONSE_DURATION: Final = "gen_ai.client.response.duration"


# litellm ``custom_llm_provider`` -> ``gen_ai.provider.name`` value.
_PROVIDER_BY_LITELLM: dict[str, GenAIProvider] = {
    "openai": GenAIProvider.OPENAI,
    "text-completion-openai": GenAIProvider.OPENAI,
    "azure": GenAIProvider.AZURE_AI_OPENAI,
    "azure_ai": GenAIProvider.AZURE_AI_INFERENCE,
    "anthropic": GenAIProvider.ANTHROPIC,
    "bedrock": GenAIProvider.AWS_BEDROCK,
    "bedrock_converse": GenAIProvider.AWS_BEDROCK,
    "vertex_ai": GenAIProvider.GCP_VERTEX_AI,
    "vertex_ai_beta": GenAIProvider.GCP_VERTEX_AI,
    "gemini": GenAIProvider.GCP_GEMINI,
    "cohere": GenAIProvider.COHERE,
    "cohere_chat": GenAIProvider.COHERE,
    "mistral": GenAIProvider.MISTRAL_AI,
    "deepseek": GenAIProvider.DEEPSEEK,
    "groq": GenAIProvider.GROQ,
    "perplexity": GenAIProvider.PERPLEXITY,
    "xai": GenAIProvider.X_AI,
    "watsonx": GenAIProvider.IBM_WATSONX_AI,
}

# litellm ``call_type`` -> ``gen_ai.operation.name``.
_OPERATION_BY_CALL_TYPE: dict[str, GenAIOperation] = {
    "completion": GenAIOperation.CHAT,
    "acompletion": GenAIOperation.CHAT,
    "completion_with_retries": GenAIOperation.CHAT,
    "text_completion": GenAIOperation.TEXT_COMPLETION,
    "atext_completion": GenAIOperation.TEXT_COMPLETION,
    "embedding": GenAIOperation.EMBEDDINGS,
    "aembedding": GenAIOperation.EMBEDDINGS,
    "responses": GenAIOperation.CHAT,
    "aresponses": GenAIOperation.CHAT,
    "call_mcp_tool": GenAIOperation.EXECUTE_TOOL,
}


def resolve_provider(custom_llm_provider: str | None) -> str:
    """Map a litellm provider string to a ``gen_ai.provider.name`` value.

    Unknown providers pass through verbatim — the convention explicitly allows
    provider-specific values, so an unmapped name is still valid.
    """
    if not custom_llm_provider:
        return ""
    mapped = _PROVIDER_BY_LITELLM.get(custom_llm_provider.lower())
    return mapped.value if mapped is not None else custom_llm_provider


def resolve_operation(call_type: str | None) -> GenAIOperation:
    """Map a litellm ``call_type`` to a ``gen_ai.operation.name`` value."""
    if not call_type:
        return GenAIOperation.CHAT
    return _OPERATION_BY_CALL_TYPE.get(call_type.lower(), GenAIOperation.CHAT)
