#  Copyright (C) 2023
#  ABM, Moscow
#
#  UNPUBLISHED PROPRIETARY MATERIAL.
#  ALL RIGHTS RESERVED.
#
#  Authors: Mike Orlov <m.orlov@abm-jsc.ru>
import abc
from dataclasses import dataclass, field
from typing import ClassVar, Any

from ..atoms import Function
from ..atoms.abstract import Selectable


@dataclass(kw_only=True, frozen=True, slots=True)
class BooleanFunction(Function, abc.ABC):
    type_: str = field(default=bool, init=False)


@dataclass(kw_only=True, frozen=True, slots=True)
class FunctionStringContains(BooleanFunction):
    args: tuple[Selectable[Any], Selectable[str]]
    code: ClassVar[str] = 'string_contains'
    ignore_case: bool = False

    def _to_sql(self, args_sql: list[str]) -> str:
        return f"{args_sql[0]} {'i' if self.ignore_case else ''}like ('%' || {args_sql[1]} || '%')"


@dataclass(kw_only=True, frozen=True, slots=True)
class FunctionIsNull(BooleanFunction):
    args: tuple[Selectable[Any]]
    code: ClassVar[str] = 'is_null'

    def _to_sql(self, args_sql: list[str]) -> str:
        return f'{args_sql[0]} IS NULL'


@dataclass(kw_only=True, frozen=True, slots=True)
class FunctionIsNotNull(BooleanFunction):
    args: tuple[Selectable[Any]]
    code: ClassVar[str] = 'is_not_null'

    def _to_sql(self, args_sql: list[str]) -> str:
        return f'{args_sql[0]} IS NOT NULL'


@dataclass(kw_only=True, frozen=True, slots=True)
class FunctionLessThan(BooleanFunction):
    args: tuple[Selectable[int | float], Selectable[int | float]]
    code: ClassVar[str] = 'lt'

    def _to_sql(self, args_sql: list[str]) -> str:
        return f'{args_sql[0]} < {args_sql[1]}'


@dataclass(kw_only=True, frozen=True, slots=True)
class FunctionLessEqual(BooleanFunction):
    args: tuple[Selectable[int | float], Selectable[int | float]]
    code: ClassVar[str] = 'le'

    def _to_sql(self, args_sql: list[str]) -> str:
        return f'{args_sql[0]} <= {args_sql[1]}'


@dataclass(kw_only=True, frozen=True, slots=True)
class FunctionEqual(BooleanFunction):
    args: tuple[Selectable[int | float], Selectable[int | float]]
    code: ClassVar[str] = 'eq'

    def _to_sql(self, args_sql: list[str]) -> str:
        return f'{args_sql[0]} = {args_sql[1]}'


@dataclass(kw_only=True, frozen=True, slots=True)
class FunctionNonEqual(BooleanFunction):
    args: tuple[Selectable[int | float], Selectable[int | float]]
    code: ClassVar[str] = 'ne'

    def _to_sql(self, args_sql: list[str]) -> str:
        return f'{args_sql[0]} != {args_sql[1]}'


@dataclass(kw_only=True, frozen=True, slots=True)
class FunctionGreaterEqual(BooleanFunction):
    args: tuple[Selectable[int | float], Selectable[int | float]]
    code: ClassVar[str] = 'ge'

    def _to_sql(self, args_sql: list[str]) -> str:
        return f'{args_sql[0]} >= {args_sql[1]}'


@dataclass(kw_only=True, frozen=True, slots=True)
class FunctionGreater(BooleanFunction):
    args: tuple[Selectable[int | float], Selectable[int | float]]
    code: ClassVar[str] = 'gt'

    def _to_sql(self, args_sql: list[str]) -> str:
        return f'{args_sql[0]} > {args_sql[1]}'


@dataclass(kw_only=True, frozen=True, slots=True)
class FunctionIn(BooleanFunction):
    args: tuple[Selectable[int | float], Selectable[list]]
    code: ClassVar[str] = 'in'

    def _to_sql(self, args_sql: list[str]) -> str:
        if args_sql[1] != "'{}'":
            return f'{args_sql[0]} = ANY ({args_sql[1]})'
        return f'FALSE'


@dataclass(kw_only=True, frozen=True, slots=True)
class FunctionNotIn(BooleanFunction):
    args: tuple[Selectable[int | float], Selectable[list]]
    code: ClassVar[str] = 'not_in'

    def _to_sql(self, args_sql: list[str]) -> str:
        if args_sql[1] != "'{}'":
            return f'{args_sql[0]} != ALL ({args_sql[1]})'
        return f'TRUE'


@dataclass(kw_only=True, frozen=True, slots=True)
class FunctionOr(BooleanFunction):
    args: tuple[Selectable[bool], ...]
    code: ClassVar[str] = 'or'

    def _to_sql(self, args_sql: list[str]) -> str:
        if not args_sql:
            return 'TRUE'
        if len(args_sql) > 1:
            return f"({' OR '.join(args_sql)})"
        return args_sql[0]


@dataclass(kw_only=True, frozen=True, slots=True)
class FunctionAnd(BooleanFunction):
    args: tuple[Selectable[bool], ...]
    code: ClassVar[str] = 'and'

    def _to_sql(self, args_sql: list[str]) -> str:
        if not args_sql:
            return 'TRUE'
        if len(args_sql) > 1:
            return f"({' AND '.join(args_sql)})"
        return args_sql[0]


@dataclass(kw_only=True, frozen=True, slots=True)
class FunctionNot(BooleanFunction):
    args: tuple[Selectable[bool]]
    code: ClassVar[str] = 'not'

    def _to_sql(self, args_sql: list[str]) -> str:
        if not args_sql:
            return 'FALSE'
        if len(args_sql) > 1:
            raise ValueError(f'{type(self).__class__} expected one arg, got: {len(args_sql)}')
        return f'NOT ({args_sql[0]})'


@dataclass(kw_only=True, frozen=True, slots=True)
class FunctionBetween(BooleanFunction):
    args: tuple[Selectable[int | float | str], Selectable[int | float | str], Selectable[int | float | str]]
    code: ClassVar[str] = 'between'

    def _to_sql(self, args_sql: list[str]) -> str:
        return f'{args_sql[0]} BETWEEN {args_sql[1]} AND {args_sql[2]}'
