# Copyright The OpenTelemetry Authors
# SPDX-License-Identifier: Apache-2.0

# pylint: disable=no-member

from typing import Final

from google.protobuf import descriptor_pb2 as descriptor

PROTO_TO_PYTHON: Final[dict[int, str]] = {
    descriptor.FieldDescriptorProto.TYPE_DOUBLE: "builtins.float",
    descriptor.FieldDescriptorProto.TYPE_FLOAT: "builtins.float",
    descriptor.FieldDescriptorProto.TYPE_INT64: "builtins.int",
    descriptor.FieldDescriptorProto.TYPE_UINT64: "builtins.int",
    descriptor.FieldDescriptorProto.TYPE_INT32: "builtins.int",
    descriptor.FieldDescriptorProto.TYPE_FIXED64: "builtins.int",
    descriptor.FieldDescriptorProto.TYPE_FIXED32: "builtins.int",
    descriptor.FieldDescriptorProto.TYPE_BOOL: "builtins.bool",
    descriptor.FieldDescriptorProto.TYPE_STRING: "builtins.str",
    descriptor.FieldDescriptorProto.TYPE_BYTES: "builtins.bytes",
    descriptor.FieldDescriptorProto.TYPE_UINT32: "builtins.int",
    descriptor.FieldDescriptorProto.TYPE_SFIXED32: "builtins.int",
    descriptor.FieldDescriptorProto.TYPE_SFIXED64: "builtins.int",
    descriptor.FieldDescriptorProto.TYPE_SINT32: "builtins.int",
    descriptor.FieldDescriptorProto.TYPE_SINT64: "builtins.int",
}

PROTO_DEFAULTS: Final[dict[int, str]] = {
    descriptor.FieldDescriptorProto.TYPE_DOUBLE: "0.0",
    descriptor.FieldDescriptorProto.TYPE_FLOAT: "0.0",
    descriptor.FieldDescriptorProto.TYPE_INT64: "0",
    descriptor.FieldDescriptorProto.TYPE_UINT64: "0",
    descriptor.FieldDescriptorProto.TYPE_INT32: "0",
    descriptor.FieldDescriptorProto.TYPE_FIXED64: "0",
    descriptor.FieldDescriptorProto.TYPE_FIXED32: "0",
    descriptor.FieldDescriptorProto.TYPE_BOOL: "False",
    descriptor.FieldDescriptorProto.TYPE_STRING: '""',
    descriptor.FieldDescriptorProto.TYPE_BYTES: 'b""',
    descriptor.FieldDescriptorProto.TYPE_UINT32: "0",
    descriptor.FieldDescriptorProto.TYPE_SFIXED32: "0",
    descriptor.FieldDescriptorProto.TYPE_SFIXED64: "0",
    descriptor.FieldDescriptorProto.TYPE_SINT32: "0",
    descriptor.FieldDescriptorProto.TYPE_SINT64: "0",
}

INT64_TYPES: Final[set[int]] = {
    descriptor.FieldDescriptorProto.TYPE_INT64,
    descriptor.FieldDescriptorProto.TYPE_UINT64,
    descriptor.FieldDescriptorProto.TYPE_FIXED64,
    descriptor.FieldDescriptorProto.TYPE_SFIXED64,
    descriptor.FieldDescriptorProto.TYPE_SINT64,
}

NUMERIC_TYPES: Final[set[int]] = {
    *INT64_TYPES,
    descriptor.FieldDescriptorProto.TYPE_DOUBLE,
    descriptor.FieldDescriptorProto.TYPE_FLOAT,
    descriptor.FieldDescriptorProto.TYPE_INT32,
    descriptor.FieldDescriptorProto.TYPE_FIXED32,
    descriptor.FieldDescriptorProto.TYPE_UINT32,
    descriptor.FieldDescriptorProto.TYPE_SFIXED32,
    descriptor.FieldDescriptorProto.TYPE_SINT32,
}

HEX_ENCODED_FIELDS: Final[set[str]] = {
    "trace_id",
    "span_id",
    "parent_span_id",
}


def get_python_type(proto_type: int) -> str:
    """
    Get the Python type corresponding to a protobuf field type.

    Args:
        proto_type: The protobuf field type as an integer (from FieldDescriptorProto).
    Returns:
        A string representing the Python type
    """
    if proto_type not in PROTO_TO_PYTHON:
        raise ValueError(f"Unknown protobuf type: {proto_type}")
    return PROTO_TO_PYTHON[proto_type]


def get_default_value(proto_type: int) -> str:
    """
    Get the default value for a protobuf field type as a string.

    Args:
        proto_type: The protobuf field type as an integer (from FieldDescriptorProto).
    Returns:
        A string representing the default value for that type
    """
    return PROTO_DEFAULTS.get(proto_type, "None")


def is_int64_type(proto_type: int) -> bool:
    """
    Check if type is a 64-bit integer type (int64, uint64, fixed64, sfixed64, sint64)

    Args:
        proto_type: The protobuf field type as an integer (from FieldDescriptorProto).
    Returns:
        True if the type is a 64-bit integer type, False otherwise.
    """
    return proto_type in INT64_TYPES


def is_bytes_type(proto_type: int) -> bool:
    """
    Check if type is bytes.

    Args:
        proto_type: The protobuf field type as an integer (from FieldDescriptorProto).
    Returns:
        True if the type is bytes, False otherwise.
    """
    return proto_type == descriptor.FieldDescriptorProto.TYPE_BYTES


def is_hex_encoded_field(field_name: str) -> bool:
    """
    Check if field is a hex-encoded field (trace_id, span_id, parent_span_id).

    Args:
        field_name: The name of the protobuf field.
    Returns:
        True if the field is a hex-encoded field, False otherwise.
    """
    return field_name in HEX_ENCODED_FIELDS


def to_json_field_name(snake_name: str) -> str:
    """
    Convert snake_case field name to camelCase for JSON representation.

    Args:
        snake_name: The field name in snake_case.
    Returns:
        The field name converted to camelCase.
    """
    components = snake_name.split("_")
    return components[0] + "".join(x.title() for x in components[1:])


def is_numeric_type(proto_type: int) -> bool:
    """
    Check if the protobuf field type is a numeric type (int32, int64, uint32, uint64, float, double).

    Args:
        proto_type: The protobuf field type as an integer (from FieldDescriptorProto).
    Returns:
        True if the type is a numeric type, False otherwise.
    """
    return proto_type in NUMERIC_TYPES


def get_json_allowed_types(proto_type: int, field_name: str = "") -> str:
    """
    Get the Python type(s) allowed for the JSON representation of a field.

    Args:
        proto_type: The protobuf field type as an integer (from FieldDescriptorProto).
        field_name: The name of the protobuf field (used to determine if it's hex-encoded)

    Returns:
        A string representing the allowed Python type(s) for the JSON representation of the field.
    """
    if is_hex_encoded_field(field_name):
        return "builtins.str"
    if is_int64_type(proto_type):
        return "(builtins.int, builtins.str)"
    if is_bytes_type(proto_type):
        return "builtins.str"
    if proto_type in (
        descriptor.FieldDescriptorProto.TYPE_FLOAT,
        descriptor.FieldDescriptorProto.TYPE_DOUBLE,
    ):
        return "(builtins.float, builtins.int, builtins.str)"
    return get_python_type(proto_type)
