import inspect
import logging
from functools import wraps
from typing import get_type_hints

"""
Framefox Framework developed by SOMA
Github: https://github.com/soma-smart/framefox
----------------------------
Author: BOUMAZA Rayen
Github: https://github.com/RayenBou
"""


class Route:
    def __init__(self, path: str, name: str, methods: list, response_model=None, tags=None):
        self.path = path
        self.name = name
        self.methods = methods
        self.response_model = response_model
        self.tags = tags or []

    def __call__(self, func):
        original_sig = inspect.signature(func)
        type_hints = get_type_hints(func)

        @wraps(func)
        async def wrapper(*args, **kwargs):
            controller_instance = args[0] if args else None

            if controller_instance and hasattr(controller_instance, "_container"):
                for param_name, param in original_sig.parameters.items():
                    if param_name == "self" or param_name in kwargs:
                        continue

                    param_type = type_hints.get(param_name)

                    if param_type and param_type != type(None):
                        if (
                            self._is_fastapi_native_type(param_type)
                            or self._is_pydantic_model(param_type)
                            or self._is_primitive_type(param_type)
                            or self._is_path_parameter(param_name)
                        ):
                            continue

                        try:
                            service = controller_instance._container.get(param_type)
                            kwargs[param_name] = service
                        except Exception as e:
                            if param.default != inspect.Parameter.empty:
                                kwargs[param_name] = param.default
                            else:
                                logger = logging.getLogger("ROUTE")
                                logger.error(f"Dependency injection failed for {param_type.__name__} in {func.__name__}.{param_name}: {e}")
                                raise RuntimeError(f"Dependency injection failed for {param_type.__name__}")

            return await func(*args, **kwargs)

        self._create_fastapi_signature(wrapper, original_sig, type_hints)

        wrapper.route_info = {
            "path": self.path,
            "name": self.name,
            "methods": self.methods,
            "response_model": self.response_model,
            "operation_ids": self._generate_operation_ids(func),
            "tags": self.tags,  # ✅ AJOUT : Tags dans route_info
            "original_function": func,
        }

        return wrapper

    def _create_fastapi_signature(self, wrapper_func, original_sig, type_hints):
        new_params = []

        for param_name, param in original_sig.parameters.items():
            if param_name == "self":
                new_params.append(param)
                continue

            param_type = type_hints.get(param_name)

            if self._is_path_parameter(param_name):
                new_params.append(param)
                continue

            if param_type and self._is_fastapi_native_type(param_type):
                new_params.append(param)
                continue

            if param_type and self._is_pydantic_model(param_type):
                new_params.append(param)
                continue

            if param_type and self._is_primitive_type(param_type):
                new_params.append(param)
                continue

            if not param_type:
                new_params.append(param)
                continue

        wrapper_func.__signature__ = original_sig.replace(parameters=new_params)

    def _is_path_parameter(self, param_name: str) -> bool:
        return f"{{{param_name}}}" in self.path

    def _is_pydantic_model(self, param_type) -> bool:
        """Check if the parameter type is a Pydantic model (static or dynamic)."""
        try:
            from pydantic import BaseModel

            if inspect.isclass(param_type) and hasattr(param_type, "__bases__") and BaseModel in getattr(param_type, "__mro__", []):
                return True

            if hasattr(param_type, "__pydantic_model__"):
                return True

            type_module = getattr(param_type, "__module__", "")
            if "pydantic" in type_module and hasattr(param_type, "__fields__"):
                return True

            return False
        except Exception:
            return False

    def _is_primitive_type(self, param_type) -> bool:
        """Check if the parameter type is a primitive type."""
        primitive_types = {int, str, float, bool, bytes}

        if param_type in primitive_types:
            return True

        if hasattr(param_type, "__origin__"):
            if param_type.__origin__ is type(None):
                return True
            if hasattr(param_type, "__args__"):
                args = getattr(param_type, "__args__", ())
                if len(args) == 2 and type(None) in args:
                    other_type = args[0] if args[1] is type(None) else args[1]
                    return other_type in primitive_types

        return False

    def _is_fastapi_native_type(self, param_type) -> bool:
        """Check if the parameter type is a FastAPI native type."""
        fastapi_native_modules = {"fastapi", "starlette"}
        fastapi_types = {
            "Request",
            "Response",
            "BackgroundTasks",
            "WebSocket",
            "HTTPException",
            "Depends",
            "Security",
            "Cookie",
            "Header",
            "Query",
            "Path",
            "Body",
            "Form",
            "File",
            "UploadFile",
        }

        type_name = getattr(param_type, "__name__", str(param_type))
        type_module = getattr(param_type, "__module__", "")

        if type_name in fastapi_types:
            return True

        for module in fastapi_native_modules:
            if type_module.startswith(module):
                return True

        return False

    def _generate_operation_ids(self, func) -> dict:
        operation_ids = {}
        path_clean = self.path.replace("/", "_").replace("{", "").replace("}", "").strip("_")
        func_name = func.__name__

        for method in self.methods:
            if path_clean:
                operation_id = f"{path_clean}_{func_name}_{method.lower()}"
            else:
                operation_id = f"{func_name}_{method.lower()}"

            operation_id = operation_id.replace("-", "_").replace(".", "_")
            operation_ids[method] = operation_id

        return operation_ids
