from typing import Type, Dict, Callable, Any, Awaitable, List, Union

from .column import Column
from .table_base import TableBase
from ..utils.camel_case_to_underscore import camel_case_to_underscore
from ..utils.escape import escape
from ..utils.is_inner import is_inner


def _get_attributes(class_to_get_attributes: Type) -> Dict[str, Column]:
    class_attrs = {}
    for attr in dir(class_to_get_attributes):
        cur_attr = getattr(class_to_get_attributes, attr)
        if not is_inner(attr) and isinstance(cur_attr, Column):
            cur_attr: Column
            class_attrs[attr] = cur_attr
            class_attrs[attr].name = attr
            class_attrs[attr].select_key_wrapper = cur_attr.clickhouse_type.select_key_wrapper
    return class_attrs


def prepare_constructor(target_type: type, class_attrs: Dict[str, Column]) -> Callable[[Any], Awaitable[TableBase]]:
    async def constructor(**kwargs):
        res = target_type()
        for attr_name in class_attrs:
            my_column = class_attrs[attr_name]
            if attr_name in kwargs:
                arg_value = kwargs[attr_name]
                try:
                    arg_value = my_column.clickhouse_type.cast_type(arg_value)
                except TypeError as e:
                    raise TypeError(f"{target_type.__name__} constructor keyword argument '{attr_name}' '{e}'")
                setattr(res, attr_name, arg_value)
            elif my_column.has_default_value():
                arg_value = await my_column.get_default_value()
                setattr(res, attr_name, arg_value)
            elif my_column.has_default_sql_value():
                pass
            else:
                raise TypeError(f"{target_type.__name__} constructor missing keyword argument '{attr_name}'")

        return res
    return constructor


def convert_to_table(class_to_convert: Type[TableBase]):
    class_to_convert.__table_name__ = camel_case_to_underscore(class_to_convert.__name__)
    class_attrs = _get_attributes(class_to_convert)
    for name in class_attrs:
        if name.startswith("__") and name.endswith("__"):
            raise KeyError(f"prohibited column name: {name}, disallowed names starting AND ending with two underscores")

    class_to_convert.__column_name_to_column__ = class_attrs
    class_to_convert.__sorted_columns__ = sorted(class_attrs.values(), key=lambda x: x.column_id)
    class_to_convert.__primary_key_columns__ = \
        list(filter(lambda x: x.primary_key, class_to_convert.__sorted_columns__))

    class_to_convert.constructor = prepare_constructor(class_to_convert, class_attrs)

    def to_insert_data(self) -> List[Union[int, float, str, list]]:
        res = []
        for column in self.__sorted_columns__:
            if hasattr(self, column.name):
                val = getattr(self, column.name)
                if hasattr(column.clickhouse_type, 'prepare_insert'):
                    val = column.clickhouse_type.prepare_insert(val)
                val = escape(val)
                res.append(val)
        return res

    def to_insert_columns(self) -> List[str]:
        res = []
        for column in self.__sorted_columns__:
            if hasattr(self, column.name):
                res.append(column.name)
        return res

    def getattr(self, key: str):
        if key.startswith("__") and key.endswith("__"):
            return object.__getattribute__(self, key)
        if key not in object.__getattribute__(self, '__dict__') and key in self.__column_name_to_column__:
            raise AttributeError(f'prevented attempt to get column instance')
        return object.__getattribute__(self, key)

    class_to_convert.__getattribute__ = getattr
    class_to_convert.to_insert_data = to_insert_data
    class_to_convert.to_insert_columns = to_insert_columns

    return class_to_convert
