import enum
from dataclasses import dataclass
from typing import Protocol

from sqlalchemy.pool import StaticPool


class DriverTypes(Protocol):
    port: int
    has_config: bool
    async_driver: str
    sync_driver: str

    config = {}

    def get_connection_uri(self, is_async: bool, cfg: 'DatabaseConfig') -> str:
        """Returns autogenerated driver uri for sqlalchemy create_engine()"""
        driver_prefix = self.async_driver if is_async else self.sync_driver
        return f'{driver_prefix}://{cfg.user}:{cfg.passwd}@{cfg.host}:{cfg.get_port()}/{cfg.name}'


class MysqlDriver(DriverTypes):
    """Driver Default values for MySQL Connection"""

    port = 3306
    async_driver = 'mysql+aiomysql'
    sync_driver = 'mysql+pymysql'
    has_config = False


class PostgresDriver(DriverTypes):
    """Driver Default values for PostgreSQL Connection"""

    port = 5432
    async_driver = 'postgresql+asyncpg'
    sync_driver = 'postgresql+psycopg2'
    has_config = False


class SqliteDriver(DriverTypes):
    port = 0
    async_driver = 'sqlite+aiosqlite'
    sync_driver = 'sqlite'
    has_config = True

    config = {
        'connect_args': {'check_same_thread': False},
        'poolclass': StaticPool,
    }

    def get_connection_uri(self, is_async: bool, cfg: 'DatabaseConfig') -> str:
        driver_prefix = self.async_driver if is_async else self.sync_driver
        return '{driver}:///{host}'.format(driver=driver_prefix, host=cfg.host)


class Driver(str, enum.Enum):
    MYSQL = 'mysql'
    POSTGRES = 'postgres'
    SQLITE = 'sqlite'


@dataclass(frozen=True)
class DatabaseConfig:
    """Database configuration params
    Obs: pass filename as name if using sqlite"""

    driver: DriverTypes
    host: str
    name: str = ''
    user: str = ''
    passwd: str = ''
    port: int = -1
    _pool_size: int = 20
    _pool_recycle: int = 3600
    _max_overflow: int = 0

    def get_port(self):
        return self.port if self.port != -1 else self.driver.port

    def get_uri(self, *, is_async: bool):
        return self.driver.get_connection_uri(is_async, self)

    @property
    def pool_config(self) -> dict[str, int]:
        if self.driver.has_config:
            return self.driver.config
        return {
            'pool_size': self._pool_size,
            'pool_recycle': self._pool_recycle,
            'max_overflow': self._max_overflow,
        }

    @classmethod
    def new(cls, driver_type: Driver, **kwargs):
        _driver_mapping = {
            Driver.MYSQL: MysqlDriver,
            Driver.POSTGRES: PostgresDriver,
            Driver.SQLITE: SqliteDriver,
        }
        return cls(driver=_driver_mapping[driver_type](), **kwargs)
