import logging
import time
from dataclasses import dataclass
from typing import Type, List

from aiohttp import BasicAuth
from http_tools.http_server_connector import HttpServerConnector
from http_tools.retry import retry

from .elements.abstract.selectable import Selectable
from .elements.output_format import OutputFormat
from .statements.abstract_statement import AbstractStatement
from .statements.count import Count
from .statements.create_table import CreateTable
from .statements.insert import Insert
from .utils.charset import Charset
from .statements.select import Select
from .elements.result import Result
from .elements.filter_by import Filter, Condition
from .elements.order import Order
from .elements.column import Column
from .elements.table import Table


logger = logging.getLogger(__file__)


class ClickHouseConnector:
    @dataclass
    class BaseConfig:
        database: str = "default"
        login: str = "default"
        password: str = ""

    @dataclass
    class Config(HttpServerConnector.Config, BaseConfig):
        compression: bool = False

    Context = HttpServerConnector.Context

    def __init__(self, config: Config, context: Context):
        self.config = config
        self._http_connector = HttpServerConnector(config, context)

    @retry(attempts_limit=2, sleep_time_sec=2.0)
    async def execute(self, sql: str) -> str:
        logger.debug(f'sql({sql}):')
        request_start_time = time.time()
        payload = await self._http_connector.post(
            '',
            payload=sql.encode(Charset.UTF8),
            headers={"Accept-Encoding": "gzip"} if self.config.compression else None,
            url_query={"enable_http_compression": 1} if self.config.compression else None,
            auth=BasicAuth(login=self.config.login, password=self.config.password)
        )
        request_end_time = time.time()
        logger.debug(f'rows got in {round(request_end_time - request_start_time, 3)} sec')
        return payload

    async def execute_statement(self, statement: AbstractStatement) -> Result:
        payload = await self.execute(sql=statement.generate_sql())
        return statement.form_result(payload=payload)

    async def select(self,
                     table: Type[Table],
                     *,
                     selectables: list[Selectable] | None = None,
                     condition: Condition | None = None,
                     group_by: List[Selectable] | None = None,
                     order_by: List[Order] | None = None,
                     offset: int | None = None,
                     limit: int | None = None,
                     output_format: OutputFormat = OutputFormat.Dict,
                     distinct_on: List[Selectable] | None = None
                     ) -> list:
        result = await self.execute_statement(
            statement=Select(
                self.config.database, table, selectables, condition, group_by,
                order_by, offset, limit, output_format, distinct_on
            )
        )
        return result.fetchall()

    async def count(self,
                    table: Type[Table],
                    *,
                    filter_by: List[Filter] | None = None,
                    search_by: List[Filter] | None = None,
                    ) -> int:
        result = await self.execute_statement(statement=Count(self.config.database, table, filter_by, search_by))
        return result.fetchone()

    async def insert(self, data_to_insert: List[Table]) -> None:
        await self.execute_statement(statement=Insert(self.config.database, data_to_insert))

    async def create_table(self, table: Type[Table]) -> None:
        await self.execute_statement(statement=CreateTable(self.config.database, table))
