from typing import Any, Optional

import orjson
from django.conf import settings
from drf_orjson_renderer.parsers import ORJSONParser
from rest_framework.exceptions import ParseError


def _underscoreize_key(key: str, no_underscore_before_number: bool = False) -> str:
    if not isinstance(key, str) or not key:
        return key
    out = []
    prev_lower = False
    for ch in key:
        if ch.isupper():
            if out and (
                prev_lower or (not no_underscore_before_number and out[-1].isdigit())
            ):
                out.append("_")
            out.append(ch.lower())
            prev_lower = False
        elif ch.isdigit():
            if out and not no_underscore_before_number and not out[-1].isdigit():
                out.append("_")
            out.append(ch)
            prev_lower = False
        else:
            out.append(ch)
            prev_lower = True
    return "".join(out)


def underscoreize(obj: Any, no_underscore_before_number: bool = False) -> Any:
    if isinstance(obj, dict):
        return {
            (
                _underscoreize_key(k, no_underscore_before_number)
                if isinstance(k, str)
                else k
            ): underscoreize(v, no_underscore_before_number)
            for k, v in obj.items()
        }
    if isinstance(obj, (list, tuple)):
        t = type(obj)
        return t(underscoreize(v, no_underscore_before_number) for v in obj)
    return obj


class CamelCaseParser(ORJSONParser):
    def parse(
        self, stream, media_type: Optional[Any] = None, parser_context: Any = None
    ) -> Any:
        parser_context = parser_context or {}
        encoding: str = parser_context.get("encoding", settings.DEFAULT_CHARSET)

        try:
            raw = stream.read().decode(encoding)
            data = orjson.loads(raw)
        except ValueError as exc:
            raise ParseError(f"JSON parse error - {exc}") from exc

        no_us_before_number = (
            getattr(settings, "REST_FRAMEWORK", {})
            .get("JSON_UNDERSCOREIZE", {})
            .get("no_underscore_before_number", False)
        )
        return underscoreize(data, no_underscore_before_number=no_us_before_number)


__all__ = ["CamelCaseParser", "underscoreize"]
