from datetime import datetime
from typing import Any, Dict
from uuid import UUID

from sqlalchemy import inspect
from sqlalchemy.ext.declarative import as_declarative, declared_attr
from sqlalchemy.orm import Query as BaseQuery
from sqlalchemy.util import symbol


@as_declarative()
class Base:
    """ORM base class"""

    id: Any
    __name__: str

    @declared_attr
    def __tablename__(cls) -> str:
        """Generate __tablename__ for the SQLAlchemy model"""
        name = ""
        if cls.__tablename__:
            name = cls.__tablename__
        else:
            name = cls.__name__.lower()
        return name

    def _is_hybrid_property(self, orm_descriptor) -> bool:
        """Returns if a model property is defined peroperty"""
        return orm_descriptor.extension_type == symbol("HYBRID_PROPERTY")

    def _is_hybrid_method(self, orm_descriptor) -> bool:
        """Returns if a model property is hybrid property"""
        return orm_descriptor.extension_type == symbol("HYBRID_METHOD")

    def _prepare_json(self, inp: Dict) -> Dict:
        """Convert a given dict to jsonable dict"""
        for key in inp.keys():
            if isinstance(inp[key], UUID):
                inp[key] = str(inp[key])
            if isinstance(inp[key], datetime):
                inp[key] = str(inp[key])
            if isinstance(inp[key], Dict):
                inp[key] = self._prepare_json(inp[key])
        return inp

    def _asdict(self, include_properties: bool = True) -> Dict[str, Any]:
        """Convert incoming SQLAlchemy model entity to a Python Dictionary

        If a SQLAlchemy model entity has joined attributes, they will be transformed to nested dictionaries

        If include_properties is set to True, the returning Dict will include
        the hybrid_properties as nested dictionaries for the given SQLAlchemy model entity
        """
        inspect_mapper = inspect(self).mapper
        orm_descriptors = inspect_mapper.all_orm_descriptors
        defined_names = [key for key, item in orm_descriptors.items()]
        results = {c: getattr(self, c) for c in set(defined_names) if self.__dict__.get(c, False)}
        if include_properties:
            hybrid_names = [
                key
                for key, item in orm_descriptors.items()
                if self._is_hybrid_property(item) or self._is_hybrid_method(item)
            ]
            for c in set(hybrid_names):
                results[c] = getattr(self, c)

        return self._prepare_json(results)


class UseSoftDelete(BaseQuery):
    _with_deleted = False

    def _get_models(self):
        if hasattr(self, "attr"):
            return [self.attr.target_mapper]
        else:
            return self._mapper_zero().class_

    def all(self):
        model_class = self._get_models()[0]
        return self.filter(model_class.deleted_at.is_(None))
