from typing import List, Optional, Collection, Union, Any, Final

from dict_caster import DictCaster, Item, OneOf

from .aggregation_function import AggregationFunction, AggregationFunctionType
from .column import Column
from .filter_by import Filter, ComparisonOperator
from .order import Order
from .output_format import OutputFormat

from ..engine.table_engine import TableEngine
from .table_base import TableBase
from ..engine.merge_tree import MergeTree
from ..exceptions import WrongArgumentsException


class AllColumns:
    pass


ALL_COLUMNS = AllColumns()

ALL_COLUMNS_COLUMN_NAME: Final[str] = "*"


OrderRepresentations = Optional[list[Union[dict[str, str], str]]]
ColumnsRepresentations = Optional[Union[List[str], AllColumns]]


class Table(TableBase):
    __table_engine__: TableEngine = MergeTree()

    @staticmethod
    async def constructor(**kwargs) -> 'Table':
        raise RuntimeError('called virtual type constructor')

    @staticmethod
    def to_insert_columns(**kwargs) -> List[str]:
        raise RuntimeError()

    @staticmethod
    def to_insert_data(self) -> List[Union[int, float, str, list]]:
        raise RuntimeError()

    @classmethod
    def columns_from_str(cls, column_names: ColumnsRepresentations) -> Optional[List[Column]]:
        if column_names is None:
            return None
        if isinstance(column_names, AllColumns):
            return list(cls.get_columns())

        columns = []
        for column_name in column_names:
            if column_name == ALL_COLUMNS_COLUMN_NAME:
                return list(cls.get_columns())
            columns.append(cls.column_from_str(column_name))
        return columns

    @classmethod
    def column_from_str(cls, column_name: str) -> Column:
        if not isinstance(column_name, str):
            raise WrongArgumentsException(
                f"wrong columns descriptor found: '{column_name}', allowed: {cls.get_column_names()})")
        elif column_name in cls.get_columns_dict():
            return cls.get_columns_dict()[column_name]
        else:
            raise WrongArgumentsException(f"column name: '{column_name}', allowed: {cls.get_column_names()})")

    @classmethod
    def aggregation_functions_from_str(cls, aggregation_dicts: Optional[Collection[dict]]) -> List[AggregationFunction]:
        if aggregation_dicts is None:
            aggregation_dicts = []
        aggregation_functions = []
        for aggregation_dict in aggregation_dicts:
            aggregation_dict = DictCaster(
                Item("attribute", cls.column_from_str),
                Item("function", AggregationFunctionType, rename="function_type"),
            ).cast(aggregation_dict)
            aggregation_functions.append(AggregationFunction(**aggregation_dict))
        return aggregation_functions

    @classmethod
    def filters_from_str(cls, filter_dicts: Optional[Collection[dict]]) -> List[Filter]:
        if filter_dicts is None:
            filter_dicts = []
        filters = []
        for filter_dict in filter_dicts:
            filter_dict = DictCaster(
                Item("attribute", cls.column_from_str),
                Item("value", Any),
                Item("operator", ComparisonOperator),
            ).cast(filter_dict)
            filters.append(Filter(**filter_dict))
        return filters

    @classmethod
    def orders_from_str(cls, order_representations: OrderRepresentations) -> List[Order]:
        if order_representations is None:
            order_representations = []
        orders = []
        ascending_default = True
        order_representation_caster = DictCaster([
                    OneOf(
                        Item("column", str, postprocessor=cls.column_from_str),
                        Item("attribute", str, rename="column", postprocessor=cls.column_from_str),
                    ),
                    Item("ascending", bool, default=ascending_default),
                ])
        for representation in order_representations:
            if isinstance(representation, str):
                order = Order(cls.column_from_str(representation), ascending_default)
            else:
                representation_dict = order_representation_caster.cast(representation)
                order = Order(**representation_dict)
            orders.append(order)
        return orders

    @classmethod
    def groups_from_str(cls, column_names: Optional[Collection[str]]) -> List[Column]:
        if column_names is None:
            column_names = []
        columns = []
        for column_name in column_names:
            columns.append(cls.column_from_str(column_name))
        return columns

    @classmethod
    def output_format_from_str(cls, output_format_name: str = None) -> OutputFormat:
        if output_format_name is None:
            output_format = OutputFormat.dict
        else:
            output_format = OutputFormat(output_format_name)
        return output_format

    @classmethod
    def set_table_engine(cls, engine: TableEngine):
        cls.__table_engine__ = engine
        engine.apply(cls)

    @classmethod
    def get_table_engine(cls) -> TableEngine:
        return cls.__table_engine__

    @classmethod
    def to_sql(cls) -> str:
        return cls.get_table_engine().construct_from_sql(cls)
