import logging
from functools import wraps
from typing import (Any,
                    Optional,
                    Callable,
                    Coroutine,
                    Dict,
                    Tuple,
                    List)

from asyncpg import PostgresError
from pymysql import Error
from sqlalchemy.engine.url import URL

MYSQL_DRIVER_NAME_PREFIX = 'mysql'
# to make pagination without limit
MYSQL_MAX_BIGINT_VALUE = 18_446_744_073_709_551_615

logger = logging.getLogger(__name__)


def handle_exceptions(function: Callable[..., Coroutine]
                      ) -> Callable[..., Coroutine]:
    @wraps(function)
    async def decorated(query: str,
                        *args: Tuple[Any, ...],
                        **kwargs: Dict[str, Any]):
        try:
            res = await function(query, *args, **kwargs)
            return res
        except (Error, PostgresError) as err:
            err_msg = ('Error while processing '
                       f'query: "{query}".')
            raise IOError(err_msg) from err

    return decorated


def is_db_uri_mysql(db_uri: URL) -> bool:
    backend_name = db_uri.get_backend_name()
    return backend_name == MYSQL_DRIVER_NAME_PREFIX


def normalize_pagination(
        *, limit: Optional[int],
        offset: Optional[int],
        is_mysql: bool) -> Tuple[Optional[int], Optional[int]]:
    if is_mysql:
        if limit is None and offset is not None:
            warn_msg = ('Incorrect pagination parameters: '
                        'in MySQL "offset" parameter '
                        'should be specified '
                        'along with "limit" parameter, '
                        'but "limit" parameter '
                        f'has value "{limit}". '
                        f'Assuming that table\'s primary key '
                        f'has "BIGINT" type '
                        f'and setting limit '
                        f'to {MYSQL_MAX_BIGINT_VALUE}.')
            logger.warning(warn_msg)
            return MYSQL_MAX_BIGINT_VALUE, offset
    return limit, offset


def generate_table_columns_names(
        *,
        columns_names: List[str],
        columns_aliases: List[str]) -> List[str]:
    return [f'{column_name} AS {column_alias}'
            for column_name, column_alias in zip(columns_names,
                                                 columns_aliases)]


def generate_table_columns_aliases(
        *,
        columns_names: List[str],
        columns_aliases: Optional[Dict[str, str]] = None) -> List[str]:
    columns_aliases = columns_aliases or {}
    return [
        f'{columns_aliases.get(column_name, column_name)}'
        for column_name in columns_names]

import logging
from functools import wraps
from typing import (Any,
                    Optional,
                    Callable,
                    Coroutine,
                    Dict,
                    Tuple,
                    List)

from asyncpg import PostgresError
from pymysql import Error
from sqlalchemy.engine.url import URL

MYSQL_DRIVER_NAME_PREFIX = 'mysql'
# to make pagination without limit
MYSQL_MAX_BIGINT_VALUE = 18_446_744_073_709_551_615

logger = logging.getLogger(__name__)


def handle_exceptions(function: Callable[..., Coroutine]
                      ) -> Callable[..., Coroutine]:
    @wraps(function)
    async def decorated(query: str,
                        *args: Tuple[Any, ...],
                        **kwargs: Dict[str, Any]):
        try:
            res = await function(query, *args, **kwargs)
            return res
        except (Error, PostgresError) as err:
            err_msg = ('Error while processing '
                       f'query: "{query}".')
            raise IOError(err_msg) from err

    return decorated


def is_db_uri_mysql(db_uri: URL) -> bool:
    backend_name = db_uri.get_backend_name()
    return backend_name == MYSQL_DRIVER_NAME_PREFIX


def normalize_pagination(
        *, limit: Optional[int],
        offset: Optional[int],
        is_mysql: bool) -> Tuple[Optional[int], Optional[int]]:
    if is_mysql:
        if limit is None and offset is not None:
            warn_msg = ('Incorrect pagination parameters: '
                        'in MySQL "offset" parameter '
                        'should be specified '
                        'along with "limit" parameter, '
                        'but "limit" parameter '
                        f'has value "{limit}". '
                        f'Assuming that table\'s primary key '
                        f'has "BIGINT" type '
                        f'and setting limit '
                        f'to {MYSQL_MAX_BIGINT_VALUE}.')
            logger.warning(warn_msg)
            return MYSQL_MAX_BIGINT_VALUE, offset
    return limit, offset


def generate_table_columns_names(
        *,
        columns_names: List[str],
        columns_aliases: List[str]) -> List[str]:
    return [f'{column_name} AS {column_alias}'
            for column_name, column_alias in zip(columns_names,
                                                 columns_aliases)]


def generate_table_columns_aliases(
        *,
        columns_names: List[str],
        columns_aliases: Optional[Dict[str, str]] = None) -> List[str]:
    columns_aliases = columns_aliases or {}
    return [
        f'{columns_aliases.get(column_name, column_name)}'
        for column_name in columns_names]

# to make pagination without limit