import json
from datetime import datetime
from decimal import Decimal
from enum import Enum
from typing import Any

from ._util import to_camel_case


def serialize(obj: Any, digits: int = None, use_names_for_enum: bool = False) -> str:
    return json.dumps(
        obj,
        cls=build_smart_json_encoder(
            digits=digits,
            use_names_for_enum=use_names_for_enum,
        ))


def build_smart_json_encoder(
    digits: int = None,
    use_names_for_enum: float = False,
):
    class SmartJsonEncoder(json.JSONEncoder):
        def default(self, o):
            if hasattr(o, '__dict__'):
                original_dict = dir(o)
                camel_dict = {}
                for prop in original_dict:
                    if prop.startswith("__") or prop.startswith("_abc_impl"):
                        continue

                    prop_value = getattr(o, prop)
                    if callable(prop_value):
                        continue

                    camel_dict[to_camel_case(prop)] = self._serialize_value(value=prop_value)

                return camel_dict

            return super().default(o)

        def _serialize_value(self, value):
            if isinstance(value, str) or isinstance(value, int):
                return value

            elif isinstance(value, float) and digits is not None:
                return round(value, digits)

            elif isinstance(value, Enum):
                if use_names_for_enum:
                    return value.name
                else:
                    return value.value
            elif isinstance(value, dict):
                new_dict = {}
                for key in value:
                    if isinstance(key, Enum):
                        new_dict[key.name] = self._serialize_value(value=value[key])
                    else:
                        new_dict[key] = self._serialize_value(value=value[key])

                return new_dict

            elif isinstance(value, list):
                ret = []
                for item in value:
                    ret.append(self._serialize_value(value=item))

                return ret

            elif isinstance(value, datetime):
                formatted_date = value.isoformat(timespec='microseconds')

                if value.tzinfo is None:
                    formatted_date += "Z"

                if formatted_date.endswith("+00:00"):
                    formatted_date = formatted_date[:-6] + "Z"

                return formatted_date

            if isinstance(value, Decimal):
                value = float(value)
                if digits is not None:
                    value = round(value, digits)

                return value

            return value

    return SmartJsonEncoder
