import uuid
from typing import Optional, Any

from psycopg import OperationalError
from psycopg_pool import PoolTimeout

from core.database.database_settings import DatabaseSettings
from core.database.postgres_pool import PostgresPool
from core.environment import Environment
from core.logger import DBLogger

logger = DBLogger(__name__)


class DBContext:
    def __init__(self):
        self._settings: Optional[DatabaseSettings] = None
        self._pool: PostgresPool = None
        self._fails = 0

    async def connect(self, database_settings: DatabaseSettings):
        try:
            logger.debug("Connecting to database")
            self._pool = PostgresPool(
                database_settings,
                Environment.get("DB_POOL_SIZE", int, 1),
            )
            logger.info("Connected to database")
        except Exception as e:
            logger.fatal("Connecting to database failed", e)

    async def execute(self, statement: str, args=None, multi=True) -> list[list]:
        logger.trace(f"execute {statement} with args: {args}")
        return await self._pool.execute(statement, args, multi)

    async def select_map(self, statement: str, args=None) -> list[dict]:
        logger.trace(f"select {statement} with args: {args}")
        try:
            return await self._pool.select_map(statement, args)
        except (OperationalError, PoolTimeout) as e:
            if self._fails >= 3:
                logger.error(f"Database error caused by `{statement}`", e)
                uid = uuid.uuid4()
                raise Exception(
                    f"Query failed three times with {type(e).__name__}. Contact an admin with the UID: {uid}"
                )

            logger.error(f"Database error caused by `{statement}`", e)
            self._fails += 1
            try:
                logger.debug("Retry select")
                return await self.select_map(statement, args)
            except Exception as e:
                pass
            return []
        except Exception as e:
            logger.error(f"Database error caused by `{statement}`", e)
            raise e

    async def select(
        self, statement: str, args=None
    ) -> list[str] | list[tuple] | list[Any]:
        logger.trace(f"select {statement} with args: {args}")
        try:
            return await self._pool.select(statement, args)
        except (OperationalError, PoolTimeout) as e:
            if self._fails >= 3:
                logger.error(f"Database error caused by `{statement}`", e)
                uid = uuid.uuid4()
                raise Exception(
                    f"Query failed three times with {type(e).__name__}. Contact an admin with the UID: {uid}"
                )

            logger.error(f"Database error caused by `{statement}`", e)
            self._fails += 1
            try:
                logger.debug("Retry select")
                return await self.select(statement, args)
            except Exception as e:
                pass
            return []
        except Exception as e:
            logger.error(f"Database error caused by `{statement}`", e)
            raise e
