import re
from typing import Any, Collection, MutableMapping

from django.utils.module_loading import import_string
from drf_orjson_renderer.renderers import ORJSONRenderer

from evibes.settings.base import MIDDLEWARE
from evibes.settings.drf import JSON_UNDERSCOREIZE

camelize_re = re.compile(r"[a-z0-9]?_[a-z0-9]")


def underscore_to_camel(match):
    group = match.group()
    if len(group) == 3:
        return group[0] + group[2].upper()
    else:
        return group[1].upper()


def _camelize_key(key: str) -> str:
    if not isinstance(key, str) or not key:
        return key
    if "_" not in key:
        return key
    parts = key.split("_")
    first = parts[0]
    rest = [p.capitalize() if p else "" for p in parts[1:]]
    return first + "".join(rest)


def camelize(obj: Any) -> Any:
    if isinstance(obj, dict):
        return {
            (_camelize_key(k) if isinstance(k, str) else k): camelize(v)
            for k, v in obj.items()
        }
    if isinstance(obj, list):
        return [camelize(v) for v in obj]
    if isinstance(obj, tuple):
        return tuple(camelize(v) for v in obj)
    return obj


def camelize_serializer_fields(result, generator, request, public):
    ignore_fields: Collection[Any] = JSON_UNDERSCOREIZE.get("ignore_fields", ())
    ignore_keys: Collection[Any] = JSON_UNDERSCOREIZE.get("ignore_keys", ())

    def has_middleware_installed():
        try:
            from evibes.middleware import CamelCaseMiddleWare
        except ImportError:
            return False

        return any(
            isinstance(m, type) and issubclass(m, CamelCaseMiddleWare)
            for m in map(import_string, MIDDLEWARE)
        )

    def camelize_str(key: str) -> str:
        new_key = re.sub(camelize_re, underscore_to_camel, key) if "_" in key else key
        if key in ignore_keys or new_key in ignore_keys:
            return key
        return new_key

    def camelize_component(
        schema: MutableMapping, name: str | None = None
    ) -> MutableMapping:
        if name is not None and (
            name in ignore_fields or camelize_str(name) in ignore_fields
        ):
            return schema
        elif schema.get("type") == "object":
            if "properties" in schema:
                schema["properties"] = {
                    camelize_str(field_name): camelize_component(
                        field_schema, field_name
                    )
                    for field_name, field_schema in schema["properties"].items()
                }
            if "required" in schema:
                schema["required"] = [
                    camelize_str(field) for field in schema["required"]
                ]
        elif schema.get("type") == "array" and isinstance(
            schema["items"], MutableMapping
        ):
            camelize_component(schema["items"])
        return schema

    for (_, component_type), component in generator.registry._components.items():
        if component_type == "schemas":
            camelize_component(component.schema)

    if has_middleware_installed():
        for url_schema in result["paths"].values():
            for method_schema in url_schema.values():
                for parameter in method_schema.get("parameters", []):
                    parameter["name"] = camelize_str(parameter["name"])

    return result


class CamelCaseRenderer(ORJSONRenderer):
    def render(
        self, data: Any, media_type: str | None = None, renderer_context: Any = None
    ) -> bytes:  # ty:ignore[invalid-method-override]
        if data is None:
            return b""

        ctx = renderer_context or {}
        camelize_disabled = ctx.get("camelize", True) is False
        payload = data if camelize_disabled else camelize(data)

        return super().render(payload, media_type=media_type, renderer_context=ctx)


__all__ = ["CamelCaseRenderer", "camelize"]
