from dataclasses import dataclass, field
from enum import unique, StrEnum
from typing import Union, Self

from dict_caster.extras import to_list

from .abstract.sqlable import Sqlable
from ..utils.escape import escape
from .column import Column


def has_any_filter_parse(value_to_parse: Union[int, list]) -> str:
    value_to_parse = to_list(value_to_parse)
    value_to_parse = [to_list(value) for value in value_to_parse]
    # TODO: Spike for replace int to Int32 in sql request
    parsed_value = []
    for value in value_to_parse:
        string = []
        for val in value:
            string.append(f"toInt32({val})")
        parsed_value.append("[" + ", ".join(string) + "]")
    has_any_filter = "[" + ", ".join(parsed_value) + "]"
    return has_any_filter


def has_filter_parse(value_to_parse: Union[int]) -> str:
    # TODO: Spike for replace int to Int32 in sql request
    # if isinstance(value_to_parse, list):
    #     raise web.HTTPBadRequest(reason="Use 'hasAny' operator to parse list")
    string = f"toInt32({value_to_parse})"
    parsed_value = ("[" + string + "]")
    return parsed_value


@unique
class ComparisonOperator(StrEnum):
    lt = "<"
    le = "<="
    ne = "!="
    eq = "="
    ge = ">="
    gt = ">"
    has_any = "hasAny"
    has = "has"
    in_ = "in"

    @classmethod
    def get_member_names(cls) -> set[str]:
        return cls._member_names_


@dataclass
class Filter(Sqlable):
    attribute: Column
    value: Union[str, int, list]
    operator: ComparisonOperator

    def to_sql(self) -> str:
        if self.operator == ComparisonOperator.has_any:
            value = has_any_filter_parse(self.value)
            return f"{self.operator}({escape(self.attribute.to_sql())}, {value})"
        elif self.operator == ComparisonOperator.has:
            value = has_filter_parse(self.value)
            return f"{self.operator}({escape(self.attribute.to_sql())}, {value})"
        elif isinstance(self.value, str):
            return f"{escape(self.attribute.to_sql())} {self.operator} '{escape(self.value)}'"
        else:
            return f"{escape(self.attribute.to_sql())} {self.operator} {self.value}"


@dataclass
class Condition(Sqlable):
    filters: list[Self, Filter] = field(default_factory=list)
    searches: list[Self | Filter] = field(default_factory=list)

    @staticmethod
    def get_condition_type():
        # TODO  consider to use (on_setattr=frozen) from attrs
        return Self | list[Self] | Filter | list[Filter] | Column | list[Column] | str \
               | int | float | list[str, int, float]

    def add_or(self, cond: Self | list[Self] | Filter | list[Filter] | Column |
               list[Column] | str | int | float | list[str, int, float]):
        self.searches += to_list(cond)

    def add_and(self, cond: Self | list[Self] | Filter | list[Filter] | Column |
                list[Column] | str | int | float | list[str, int, float]):
        self.filters += to_list(cond)

    def to_sql(self) -> str:
        sql = self._form_filter_sql()
        search_sql = self._form_search_sql()
        if search_sql and sql:
            sql += " AND "
        sql += f"{search_sql}"
        return sql

    def _form_filter_sql(self) -> str:
        filters_sql = []
        for filter_ in self.filters:

            if isinstance(filter_, Condition):
                filter_sql = f"({filter_.to_sql()})"
            else:
                filter_sql = filter_.to_sql()
            filters_sql.append(filter_sql)

        return " AND ".join(filters_sql)

    def _form_search_sql(self) -> str:
        searches_sql = []
        for search in self.searches:

            if isinstance(search, Condition):
                filter_sql = f"({search.to_sql()})"
            else:
                filter_sql = f"{search.to_sql()}"
            searches_sql.append(filter_sql)

        return " OR ".join(searches_sql)

    def __bool__(self):
        return bool(self.searches) or bool(self.filters)
