from typing import Optional

import aiomysql
from aiomysql import InterfaceError, MySQLError

from .base import (
    CommandParams,
    ClientClass,
    Result,
)


class MysqlClient(ClientClass):
    ENGINE = 'MySQL'

    SQL_FUNCTIONS = [
        'CONCAT', 'GROUP_CONCAT', 'UNIX_TIMESTAMP', 'FROM_UNIXTIME', 'DATE_FORMAT'
    ]

    def __init__(
        self, host: str, username: str, password: str, dbname: str,
        port: Optional[str] = None, unix_socket: Optional[str] = None
    ):
        super().__init__(host, username, password, dbname, port, unix_socket)
        if not port:
            self.port = '3306'

    async def connect(self):
        params = {
            'user': self.username,
            'db': self.dbname,
            'autocommit': True
        }

        if self.password:
            params['password'] = self.password

        if not self.unix_socket:
            params['host'] = self.host
            params['port'] = int(self.port)
        else:
            params['unix_socket'] = self.unix_socket

        self.connection = await aiomysql.connect(**params)

    async def change_database(self, database: str):
        self.connection = None
        return await super().change_database(database)

    async def get_table_columns(self, table_name: str, database: str = None):
        db_name = database or self.dbname
        result = await self.execute(f"""
            SELECT column_name
            FROM information_schema.columns
            WHERE table_name = '{table_name}'
            AND table_schema = '{db_name}'
            ORDER BY ordinal_position
        """)

        return [f"{row['COLUMN_NAME']}" for row in result.data]

    async def get_tables(self, database: Optional[str] = None) -> Result:
        if not database:
            database = self.dbname

        result = await self.execute('SHOW TABLES IN %s' % database)

        if result.data:
            result.data = [{'table': next(iter(x.values())), 'database': database} for x in result.data]
        return result

    async def get_databases(self) -> Result:
        result = await self.execute('SHOW DATABASES')
        if result.data:
            result.data = [{'database': next(iter(x.values()))} for x in result.data]
        return result

    async def get_schema(self, table: str, database: Optional[str] = None) -> Result:
        if not database:
            database = self.dbname

        result = await self.execute('SHOW CREATE TABLE `%s`.`%s`' % (database or self.dbname, table))

        if result and result.data:
            result.data = [{'schema': list(x.values())[-1]} for x in result.data]
        return result

    def get_sample_data_sql(self,
        table: str,
        database: Optional[str] = None,
    ):
        return f"SELECT * FROM `{database}`.`{table}`"

    def get_limit_sql(self, limit: int, offset: int = 0):
        return f'LIMIT {offset},{limit}'

    async def command_schema(self, command: CommandParams):
        table = command.params
        return await self.execute('SHOW CREATE TABLE %s' % table)

    def is_db_error_exception(self, exc: Exception) -> bool:
        return isinstance(exc, MySQLError)

    async def execute(self, sql) -> Result:
        result = await self.if_command_process(sql)

        if result:
            return result

        for tries in range(2):
            try:

                if self.connection is None:
                    await self.connect()

                async with self.connection.cursor(aiomysql.DictCursor) as cur:
                    await cur.execute(sql)
                    data = await cur.fetchall()

                    return Result(data, cur.rowcount)
            except InterfaceError as exc:
                self.connection = None

                if tries == 1:
                    raise exc
