import logging

from datetime import date, datetime, UTC
from typing import Optional
from decimal import Decimal

import aiomysql

from src.ezgg_lan_manager.types.CateringOrder import CateringOrder
from src.ezgg_lan_manager.types.CateringMenuItem import CateringMenuItem, CateringMenuItemCategory
from src.ezgg_lan_manager.types.CateringOrder import CateringMenuItemsWithAmount, CateringOrderStatus
from src.ezgg_lan_manager.types.ConfigurationTypes import DatabaseConfiguration
from src.ezgg_lan_manager.types.News import News
from src.ezgg_lan_manager.types.Participant import Participant
from src.ezgg_lan_manager.types.Seat import Seat
from src.ezgg_lan_manager.types.Team import TeamStatus, Team
from src.ezgg_lan_manager.types.Ticket import Ticket
from src.ezgg_lan_manager.types.Tournament import Tournament
from src.ezgg_lan_manager.types.TournamentBase import GameTitle, TournamentFormat, TournamentStatus, ParticipantType, MatchStatus
from src.ezgg_lan_manager.types.Transaction import Transaction
from src.ezgg_lan_manager.types.User import User

logger = logging.getLogger(__name__.split(".")[-1])


class DuplicationError(Exception):
    pass


class NoDatabaseConnectionError(Exception):
    pass


class DatabaseService:
    MAX_CONNECTION_RETRIES = 5

    def __init__(self, database_config: DatabaseConfiguration) -> None:
        self._database_config = database_config
        self._connection_pool: Optional[aiomysql.Pool] = None

    async def is_healthy(self) -> bool:
        try:
            async with self._connection_pool.acquire() as conn:
                async with conn.cursor() as _:
                    return True
        except aiomysql.OperationalError:
            return False
        except Exception as e:
            logger.error(f"Failed to acquire a connection: {e}")
            return False

    async def init_db_pool(self) -> bool:
        logger.info(
            f"Connecting to database '{self._database_config.db_name}' on "
            f"{self._database_config.db_user}@{self._database_config.db_host}:{self._database_config.db_port}"
        )
        try:
            self._connection_pool = await aiomysql.create_pool(
                host=self._database_config.db_host,
                port=self._database_config.db_port,
                user=self._database_config.db_user,
                password=self._database_config.db_password,
                db=self._database_config.db_name,
                minsize=1,
                maxsize=40,
                autocommit=True
            )
        except aiomysql.OperationalError:
            return False
        return True

    @staticmethod
    def _map_db_result_to_user(data: tuple) -> User:
        return User(
            user_id=data[0],
            user_name=data[1],
            user_mail=data[2],
            user_password=data[3],
            user_fallback_password=data[4],
            user_first_name=data[5],
            user_last_name=data[6],
            user_birth_day=data[7],
            is_active=bool(data[8]),
            is_team_member=bool(data[9]),
            is_admin=bool(data[10]),
            created_at=data[11],
            last_updated_at=data[12]
        )

    @staticmethod
    def _parse_tournament_format(format_as_string: str) -> TournamentFormat:
        if format_as_string == "SE_BO_1":
            return TournamentFormat.SINGLE_ELIMINATION_BO_1
        elif format_as_string == "SE_BO_3":
            return TournamentFormat.SINGLE_ELIMINATION_BO_3
        elif format_as_string == "SE_BO_5":
            return TournamentFormat.SINGLE_ELIMINATION_BO_5
        elif format_as_string == "DE_BO_1":
            return TournamentFormat.DOUBLE_ELIMINATION_BO_1
        elif format_as_string == "DE_BO_3":
            return TournamentFormat.DOUBLE_ELIMINATION_BO_3
        elif format_as_string == "DE_BO_5":
            return TournamentFormat.DOUBLE_ELIMINATION_BO_5
        elif format_as_string == "FFA":
            return TournamentFormat.FFA
        else:
            # If this happens, database is FUBAR
            raise RuntimeError(f"Unknown TournamentFormat: {format_as_string}")

    @staticmethod
    def _parse_tournament_status(status_as_string: str) -> TournamentStatus:
        if status_as_string == "CLOSED":
            return TournamentStatus.CLOSED
        elif status_as_string == "OPEN":
            return TournamentStatus.OPEN
        elif status_as_string == "COMPLETED":
            return TournamentStatus.COMPLETED
        elif status_as_string == "CANCELED":
            return TournamentStatus.CANCELED
        elif status_as_string == "INVITE_ONLY":
            return TournamentStatus.INVITE_ONLY
        elif status_as_string == "ONGOING":
            return TournamentStatus.ONGOING
        else:
            # If this happens, database is FUBAR
            raise RuntimeError(f"Unknown TournamentStatus: {status_as_string}")

    @staticmethod
    def _parse_participant_type(participant_type_as_string: str) -> ParticipantType:
        if participant_type_as_string == "PLAYER":
            return ParticipantType.PLAYER
        elif participant_type_as_string == "TEAM":
            return ParticipantType.TEAM
        else:
            # If this happens, database is FUBAR
            raise RuntimeError(f"Unknown ParticipantType: {participant_type_as_string}")

    async def get_user_by_name(self, user_name: str) -> Optional[User]:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                await cursor.execute("SELECT * FROM users WHERE user_name=%s", (user_name,))
                result = await cursor.fetchone()
                if not result:
                    return
                return self._map_db_result_to_user(result)

    async def get_user_by_id(self, user_id: int) -> Optional[User]:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                await cursor.execute("SELECT * FROM users WHERE user_id=%s", (user_id,))
                result = await cursor.fetchone()
                if not result:
                    return
                return self._map_db_result_to_user(result)

    async def get_user_by_mail(self, user_mail: str) -> Optional[User]:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                await cursor.execute("SELECT * FROM users WHERE user_mail=%s", (user_mail.lower(),))
                result = await cursor.fetchone()
                if not result:
                    return
                return self._map_db_result_to_user(result)

    async def create_user(self, user_name: str, user_mail: str, password_hash: str) -> User:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute(
                        "INSERT INTO users (user_name, user_mail, user_password) "
                        "VALUES (%s, %s, %s)", (user_name, user_mail.lower(), password_hash)
                    )
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.create_user(user_name, user_mail, password_hash)
                except aiomysql.IntegrityError as e:
                    logger.warning(f"Aborted duplication entry: {e}")
                    raise DuplicationError

        return await self.get_user_by_name(user_name)

    async def update_user(self, user: User) -> User:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute(
                        "UPDATE users SET user_name=%s, user_mail=%s, user_password=%s, user_fallback_password=%s,"
                        "user_first_name=%s, user_last_name=%s, user_birth_date=%s, is_active=%s, is_team_member=%s,"
                        " is_admin=%s WHERE (user_id=%s)",
                        (user.user_name, user.user_mail.lower(), user.user_password, user.user_fallback_password,
                         user.user_first_name, user.user_last_name, user.user_birth_day,
                         user.is_active, user.is_team_member, user.is_admin,
                         user.user_id)
                    )
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.update_user(user)
                except aiomysql.IntegrityError as e:
                    logger.warning(f"Aborted duplication entry: {e}")
                    raise DuplicationError
                return user

    async def add_transaction(self, transaction: Transaction) -> Optional[Transaction]:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute(
                        "INSERT INTO transactions (user_id, value, is_debit, transaction_date, transaction_reference) "
                        "VALUES (%s, %s, %s, %s, %s)",
                        (transaction.user_id, transaction.value, transaction.is_debit, transaction.transaction_date,
                         transaction.reference)
                    )
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.add_transaction(transaction)
                except Exception as e:
                    logger.warning(f"Error adding Transaction: {e}")
                    return

                return transaction

    async def get_all_transactions_for_user(self, user_id: int) -> list[Transaction]:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                transactions = []
                try:
                    await cursor.execute("SELECT * FROM transactions WHERE user_id=%s", (user_id,))
                    await conn.commit()
                    result = await cursor.fetchall()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.get_all_transactions_for_user(user_id)
                except aiomysql.Error as e:
                    logger.error(f"Error getting all transactions for user: {e}")
                    return []

                for transaction_raw in result:
                    transactions.append(Transaction(
                        user_id=user_id,
                        value=Decimal(transaction_raw[2]),
                        is_debit=bool(transaction_raw[3]),
                        transaction_date=transaction_raw[4],
                        reference=transaction_raw[5]
                    ))
                return transactions

    async def add_news(self, news: News) -> None:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute(
                        "INSERT INTO news (news_content, news_title, news_subtitle, news_author, news_date) "
                        "VALUES (%s, %s, %s, %s, %s)",
                        (news.content, news.title, news.subtitle, news.author.user_id, news.news_date)
                    )
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.add_news(news)
                except Exception as e:
                    logger.warning(f"Error adding Transaction: {e}")

    async def get_news(self, dt_start: date, dt_end: date) -> list[News]:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                results = []
                try:
                    await cursor.execute(
                        "SELECT * FROM news INNER JOIN users ON news.news_author = users.user_id WHERE news_date"
                        " BETWEEN %s AND %s;",
                        (dt_start, dt_end))
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.get_news(dt_start, dt_end)
                except Exception as e:
                    logger.warning(f"Error fetching news: {e}")
                    return []

                for news_raw in await cursor.fetchall():
                    user = self._map_db_result_to_user(news_raw[6:])
                    results.append(News(
                        news_id=news_raw[0],
                        title=news_raw[2],
                        subtitle=news_raw[3],
                        author=user,
                        content=news_raw[1],
                        news_date=news_raw[5]
                    ))

                return results

    async def update_news(self, news: News) -> None:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute(
                        """
                        UPDATE news
                        SET news_content = %s, 
                            news_title = %s, 
                            news_subtitle = %s, 
                            news_author = %s, 
                            news_date = %s
                        WHERE news_id = %s
                        """,
                        (news.content, news.title, news.subtitle, news.author.user_id, news.news_date, news.news_id)
                    )
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.update_news(news)
                except Exception as e:
                    logger.warning(f"Error updating news: {e}")

    async def remove_news(self, news_id: int) -> None:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute(
                        "DELETE FROM news WHERE news_id = %s",
                        (news_id,)
                    )
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.remove_news(news_id)
                except Exception as e:
                    logger.warning(f"Error removing news with ID {news_id}: {e}")

    async def get_tickets(self) -> list[Ticket]:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                results = []
                try:
                    await cursor.execute("SELECT * FROM tickets INNER JOIN users ON tickets.user = users.user_id;", ())
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.get_tickets()
                except Exception as e:
                    logger.warning(f"Error fetching tickets: {e}")
                    return []

                for ticket_raw in await cursor.fetchall():
                    user = self._map_db_result_to_user(ticket_raw[3:])
                    results.append(Ticket(
                        ticket_id=ticket_raw[0],
                        category=ticket_raw[1],
                        purchase_date=ticket_raw[3],
                        owner=user
                    ))

                return results

    async def get_ticket_for_user(self, user_id: int) -> Optional[Ticket]:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute(
                        "SELECT * FROM tickets INNER JOIN users ON tickets.user = users.user_id WHERE user_id=%s;",
                        (user_id,))
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.get_ticket_for_user(user_id)
                except Exception as e:
                    logger.warning(f"Error fetching ticket for user: {e}")
                    return

                result = await cursor.fetchone()
                if not result:
                    return

                user = self._map_db_result_to_user(result[3:])
                return Ticket(
                    ticket_id=result[0],
                    category=result[1],
                    purchase_date=result[3],
                    owner=user
                )

    async def generate_ticket_for_user(self, user_id: int, category: str) -> Optional[Ticket]:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute("INSERT INTO tickets (ticket_category, user) VALUES (%s, %s)",
                                         (category, user_id))
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.generate_ticket_for_user(user_id, category)
                except Exception as e:
                    logger.warning(f"Error generating ticket for user: {e}")
                    return

                return await self.get_ticket_for_user(user_id)

    async def change_ticket_owner(self, ticket_id: int, new_owner_id: int) -> bool:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute("UPDATE tickets SET user = %s WHERE ticket_id = %s;",
                                         (new_owner_id, ticket_id))
                    affected_rows = cursor.rowcount
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.change_ticket_owner(ticket_id, new_owner_id)
                except Exception as e:
                    logger.warning(f"Error transferring ticket to user: {e}")
                    return False
                return affected_rows > 0

    async def delete_ticket(self, ticket_id: int) -> bool:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute("DELETE FROM tickets WHERE ticket_id = %s;", (ticket_id,))
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.delete_ticket(ticket_id)
                except Exception as e:
                    logger.warning(f"Error deleting ticket: {e}")
                    return False
                return True

    async def generate_fresh_seats_table(self, seats: list[tuple[str, str]]) -> None:
        """ WARNING: THIS WILL DELETE ALL EXISTING DATA! DO NOT USE ON PRODUCTION DATABASE! """
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute("TRUNCATE seats;")
                    for seat in seats:
                        await cursor.execute("INSERT INTO seats (seat_id, seat_category) VALUES (%s, %s);",
                                             (seat[0], seat[1]))
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.generate_fresh_seats_table(seats)
                except Exception as e:
                    logger.warning(f"Error generating fresh seats table: {e}")
                    return

    async def get_seating_info(self) -> list[Seat]:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                results = []
                try:
                    await cursor.execute(
                        "SELECT seats.*, users.* FROM seats LEFT JOIN users ON seats.user = users.user_id;")
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.get_seating_info()
                except Exception as e:
                    logger.warning(f"Error getting seats table: {e}")
                    return results

                for seat_raw in await cursor.fetchall():
                    if seat_raw[3] is None:  # Empty seat
                        results.append(Seat(seat_raw[0], bool(seat_raw[1]), seat_raw[2], None))
                    else:
                        user = self._map_db_result_to_user(seat_raw[4:])
                        results.append(Seat(seat_raw[0], bool(seat_raw[1]), seat_raw[2], user))

                return results

    async def seat_user(self, seat_id: str, user_id: int) -> bool:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute("UPDATE seats SET user = %s WHERE seat_id = %s;", (user_id, seat_id))
                    affected_rows = cursor.rowcount
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.seat_user(seat_id, user_id)
                except Exception as e:
                    logger.warning(f"Error seating user: {e}")
                    return False
                return affected_rows > 0

    async def get_menu_items(self) -> list[CateringMenuItem]:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                results = []
                try:
                    await cursor.execute("SELECT * FROM catering_menu_items;")
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.get_menu_items()
                except Exception as e:
                    logger.warning(f"Error fetching menu items: {e}")
                    return results

                for menu_item_raw in await cursor.fetchall():
                    results.append(CateringMenuItem(
                        item_id=menu_item_raw[0],
                        name=menu_item_raw[1],
                        additional_info=menu_item_raw[2],
                        price=Decimal(menu_item_raw[3]),
                        category=CateringMenuItemCategory(menu_item_raw[4]),
                        is_disabled=bool(menu_item_raw[5])
                    ))

                return results

    async def get_menu_item(self, menu_item_id) -> Optional[CateringMenuItem]:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute("SELECT * FROM catering_menu_items WHERE catering_menu_item_id = %s;",
                                         (menu_item_id,))
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.get_menu_item(menu_item_id)
                except Exception as e:
                    logger.warning(f"Error fetching menu items: {e}")
                    return

                raw_data = await cursor.fetchone()
                if raw_data is None:
                    return
                return CateringMenuItem(
                    item_id=raw_data[0],
                    name=raw_data[1],
                    additional_info=raw_data[2],
                    price=Decimal(raw_data[3]),
                    category=CateringMenuItemCategory(raw_data[4]),
                    is_disabled=bool(raw_data[5])
                )

    async def add_menu_item(self, name: str, info: str, price: Decimal, category: CateringMenuItemCategory,
                            is_disabled: bool = False) -> Optional[CateringMenuItem]:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute(
                        "INSERT INTO catering_menu_items (name, additional_info, price, category, is_disabled) VALUES "
                        "(%s, %s, %s, %s, %s);",
                        (name, info, price, category.value, is_disabled)
                    )
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.add_menu_item(name, info, price, category, is_disabled)
                except Exception as e:
                    logger.warning(f"Error adding menu item: {e}")
                    return

                return CateringMenuItem(
                    item_id=cursor.lastrowid,
                    name=name,
                    additional_info=info,
                    price=price,
                    category=category,
                    is_disabled=is_disabled
                )

    async def delete_menu_item(self, menu_item_id: int) -> bool:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute("DELETE FROM catering_menu_items WHERE catering_menu_item_id = %s;",
                                         (menu_item_id,))
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.delete_menu_item(menu_item_id)
                except Exception as e:
                    logger.warning(f"Error deleting menu item: {e}")
                    return False
                return cursor.affected_rows > 0

    async def update_menu_item(self, updated_item: CateringMenuItem) -> bool:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute(
                        "UPDATE catering_menu_items SET name = %s, additional_info = %s, price = %s, category = %s, "
                        "is_disabled = %s WHERE catering_menu_item_id = %s;",
                        (updated_item.name, updated_item.additional_info, updated_item.price,
                         updated_item.category.value, updated_item.is_disabled, updated_item.item_id)
                    )
                    affected_rows = cursor.rowcount
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.update_menu_item(updated_item)
                except Exception as e:
                    logger.warning(f"Error updating menu item: {e}")
                    return False
                return affected_rows > 0

    async def add_new_order(self, menu_items: CateringMenuItemsWithAmount, user_id: int, is_delivery: bool) -> Optional[CateringOrder]:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                now = datetime.now()
                try:
                    await cursor.execute(
                        "INSERT INTO orders (status, user, is_delivery, order_date) VALUES (%s, %s, %s, %s);",
                        (CateringOrderStatus.RECEIVED.value, user_id, is_delivery, now)
                    )
                    order_id = cursor.lastrowid
                    for menu_item, quantity in menu_items.items():
                        await cursor.execute(
                            "INSERT INTO order_catering_menu_item (order_id, catering_menu_item_id, quantity) VALUES "
                            "(%s, %s, %s);",
                            (order_id, menu_item.item_id, quantity)
                        )
                    await conn.commit()
                    return CateringOrder(
                        order_id=order_id,
                        order_date=now,
                        status=CateringOrderStatus.RECEIVED,
                        items=menu_items,
                        customer=await self.get_user_by_id(user_id),
                        is_delivery=is_delivery
                    )
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.add_new_order(menu_items, user_id, is_delivery)
                except Exception as e:
                    logger.warning(f"Error placing order: {e}")
                    return

    async def change_order_status(self, order_id: int, status: CateringOrderStatus) -> bool:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute(
                        "UPDATE orders SET status = %s WHERE order_id = %s;",
                        (status.value, order_id)
                    )
                    affected_rows = cursor.rowcount
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.change_order_status(order_id, status)
                except Exception as e:
                    logger.warning(f"Error updating menu item: {e}")
                    return False
                return affected_rows > 0

    async def get_orders(self, user_id: Optional[int] = None, status: Optional[CateringOrderStatus] = None) -> list[CateringOrder]:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                fetched_orders = []
                query = "SELECT * FROM orders LEFT JOIN users ON orders.user = users.user_id"
                if user_id is not None and status is None:
                    query += f" WHERE user = {user_id};"
                elif status is not None and user_id is None:
                    query += f" WHERE status = '{status.value}';"
                elif status is not None and user_id is not None:
                    query += f" WHERE user = {user_id} AND status = '{status.value}';"
                else:
                    query += ";"
                try:
                    await cursor.execute(query)
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.get_orders(user_id, status)
                except Exception as e:
                    logger.warning(f"Error getting orders: {e}")
                    return fetched_orders

                for raw_order in await cursor.fetchall():
                    fetched_orders.append(
                        CateringOrder(
                            order_id=raw_order[0],
                            status=CateringOrderStatus(raw_order[1]),
                            customer=self._map_db_result_to_user(raw_order[5:]),
                            items=await self.get_menu_items_for_order(raw_order[0]),
                            is_delivery=bool(raw_order[4]),
                            order_date=raw_order[3],
                        )
                    )

                return fetched_orders

    async def get_menu_items_for_order(self, order_id: int) -> CateringMenuItemsWithAmount:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                result = {}
                try:
                    await cursor.execute(
                        "SELECT * FROM order_catering_menu_item "
                        "LEFT JOIN catering_menu_items ON order_catering_menu_item.catering_menu_item_id = catering_menu_items.catering_menu_item_id "
                        "WHERE order_id = %s;",
                        (order_id,)
                    )
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.get_menu_items_for_order(order_id)
                except Exception as e:
                    logger.warning(f"Error getting order items: {e}")
                    return result

                for order_catering_menu_item_raw in await cursor.fetchall():
                    result[CateringMenuItem(
                        item_id=order_catering_menu_item_raw[1],
                        name=order_catering_menu_item_raw[4],
                        additional_info=order_catering_menu_item_raw[5],
                        price=Decimal(order_catering_menu_item_raw[6]),
                        category=CateringMenuItemCategory(order_catering_menu_item_raw[7]),
                        is_disabled=bool(order_catering_menu_item_raw[8])
                    )] = order_catering_menu_item_raw[2]

                return result

    async def set_user_profile_picture(self, user_id: int, picture_data: bytes) -> None:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute(
                        "INSERT INTO user_profile_picture (user_id, picture) VALUES (%s, %s) ON DUPLICATE KEY UPDATE picture = VALUES(picture)",
                        (user_id, picture_data)
                    )
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.set_user_profile_picture(user_id, picture_data)
                except Exception as e:
                    logger.warning(f"Error setting user profile picture: {e}")

    async def get_user_profile_picture(self, user_id: int) -> Optional[bytes]:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute("SELECT (picture) FROM user_profile_picture WHERE user_id = %s", (user_id,))
                    await conn.commit()
                    r = await cursor.fetchone()
                    if r is None:
                        return
                    return r[0]
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.get_user_profile_picture(user_id)
                except Exception as e:
                    logger.warning(f"Error setting user profile picture: {e}")
                    return None

    async def get_all_users(self) -> list[User]:

        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                results = []
                try:
                    await cursor.execute("SELECT * FROM users;")
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.get_all_users()
                except Exception as e:
                    logger.warning(f"Error getting all users: {e}")
                    return results

                for user_raw in await cursor.fetchall():
                    results.append(self._map_db_result_to_user(user_raw))

        return results

    async def remove_profile_picture(self, user_id: int):
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute(
                        "DELETE FROM user_profile_picture WHERE user_id = %s",
                        user_id
                    )
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.remove_profile_picture(user_id)
                except Exception as e:
                    logger.warning(f"Error deleting user profile picture: {e}")

    async def get_all_tournaments(self) -> list[Tournament]:
        logger.info(f"Polling Tournaments...")
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.DictCursor) as cursor:
                try:
                    await cursor.execute(
                        """
                        SELECT
                        /* =======================
                           Tournament
                           ======================= */
                        t.id                AS tournament_id,
                        t.name              AS tournament_name,
                        t.description       AS tournament_description,
                        t.format            AS tournament_format,
                        t.start_time,
                        t.status            AS tournament_status,
                        t.max_participants,
                        t.created_at,
                        t.participant_type  AS tournament_participant_type,
                    
                        /* =======================
                           Game Title
                           ======================= */
                        gt.id               AS game_title_id,
                        gt.name             AS game_title_name,
                        gt.description      AS game_title_description,
                        gt.web_link         AS game_title_web_link,
                        gt.image_name       AS game_title_image_name,
                    
                        /* =======================
                           Tournament Participant
                           ======================= */
                        tp.id               AS participant_id,
                        tp.user_id,
                        tp.team_id,
                        tp.participant_type,
                        tp.seed,
                        tp.joined_at
                    
                    FROM tournaments t
                    JOIN game_titles gt
                        ON gt.id = t.game_title_id
                    
                    LEFT JOIN tournament_participants tp
                        ON tp.tournament_id = t.id
                    
                    ORDER BY
                        t.id,
                        tp.seed IS NULL,
                        tp.seed;

                        """
                    )
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.get_all_tournaments()
                except Exception as e:
                    logger.warning(f"Error getting tournaments: {e}")

                tournaments = []
                current_tournament: Optional[Tournament] = None
                for row in await cursor.fetchall():
                    if current_tournament is None or current_tournament.id != row["tournament_id"]:
                        if current_tournament is not None:
                            tournaments.append(current_tournament)
                        participant_type = self._parse_participant_type(row["tournament_participant_type"])
                        id_accessor = "user_id" if participant_type == ParticipantType.PLAYER else "team_id"
                        current_tournament = Tournament(
                            id_=row["tournament_id"],
                            name=row["tournament_name"],
                            description=row["tournament_description"],
                            game_title=GameTitle(
                                name=row["game_title_name"],
                                description=row["game_title_description"],
                                web_link=row["game_title_web_link"],
                                image_name=row["game_title_image_name"]
                            ),
                            format_=self._parse_tournament_format(row["tournament_format"]),
                            start_time=row["start_time"],
                            status=self._parse_tournament_status(row["tournament_status"]),
                            participants=[Participant(id_=row[id_accessor], participant_type=self._parse_participant_type(row["participant_type"]))] if row[id_accessor] is not None else [],
                            matches=None,  # ToDo: Implement
                            rounds=[],  # ToDo: Implement
                            max_participants=row["max_participants"],
                            participant_type=participant_type
                        )
                    else:
                        id_accessor = "user_id" if current_tournament.participant_type == ParticipantType.PLAYER else "team_id"
                        current_tournament.add_participant(
                            Participant(id_=row[id_accessor], participant_type=self._parse_participant_type(row["participant_type"]))
                        )
                else:
                    tournaments.append(current_tournament)

            return tournaments

    async def add_participant_to_tournament(self, participant: Participant, tournament: Tournament) -> None:
        if participant.participant_type != tournament.participant_type:
            raise ValueError(f"Can not add {participant.participant_type.name} to {tournament.participant_type.name} tournament")
        accessor = "user_id" if participant.participant_type == ParticipantType.PLAYER else "team_id"
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute(
                        f"INSERT INTO tournament_participants (tournament_id, {accessor}, participant_type) VALUES (%s, %s, %s);",
                        (tournament.id, participant.id, participant.participant_type.name)
                    )
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.add_participant_to_tournament(participant, tournament)
                except Exception as e:
                    logger.warning(f"Error adding participant to tournament: {e}")

    async def remove_participant_from_tournament(self, participant: Participant, tournament: Tournament) -> None:
        accessor = "user_id" if participant.participant_type == ParticipantType.PLAYER else "team_id"
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute(
                        f"DELETE FROM tournament_participants WHERE (tournament_id = %s AND {accessor} = %s);",
                        (tournament.id, participant.id)
                    )
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.remove_participant_from_tournament(participant, tournament)
                except Exception as e:
                    logger.warning(f"Error removing participant from tournament: {e}")

    async def get_teams(self) -> list[Team]:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                query = """
                SELECT
                    t.id            AS team_id,
                    t.name          AS team_name,
                    t.abbreviation  AS team_abbr,
                    t.join_password,
                    t.created_at    AS team_created_at,
                
                    tm.status       AS team_status,
                    tm.joined_at    AS member_joined_at,
                
                    u.*
                
                FROM teams t
                
                LEFT JOIN team_members tm
                       ON t.id = tm.team_id
                
                LEFT JOIN users u
                       ON tm.user_id = u.user_id
                
                ORDER BY
                    t.id,
                    CASE tm.status
                        WHEN 'LEADER'  THEN 1
                        WHEN 'OFFICER' THEN 2
                        WHEN 'MEMBER'  THEN 3
                        ELSE 4
                    END,
                    u.user_name;
                """
                try:
                    await cursor.execute(query)
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.get_teams()
                except Exception as e:
                    logger.warning(f"Error getting teams: {e}")
                    return []

                current_team: Optional[Team] = None
                all_teams = []

                for row in await cursor.fetchall():
                    if row[5] is None:  # Teams without single member are ignored
                        continue
                    if current_team is None:
                        user = self._map_db_result_to_user(row[7:])
                        current_team = Team(id=row[0], name=row[1], abbreviation=row[2], join_password=row[3], members={user: TeamStatus.from_str(row[5])})
                    elif current_team.id == row[0]:  # Still same team
                        current_team.members[self._map_db_result_to_user(row[7:])] = TeamStatus.from_str(row[5])
                    else:
                        all_teams.append(current_team)
                        user = self._map_db_result_to_user(row[7:])
                        current_team = Team(id=row[0], name=row[1], abbreviation=row[2], join_password=row[3], members={user: TeamStatus.from_str(row[5])})

                if current_team is not None:
                    all_teams.append(current_team)

                return all_teams

    async def get_team_by_id(self, team_id: int) -> Optional[Team]:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                query = """
                SELECT
                    t.id            AS team_id,
                    t.name          AS team_name,
                    t.abbreviation  AS team_abbr,
                    t.join_password,
                    t.created_at    AS team_created_at,
                
                    tm.status       AS team_status,
                    tm.joined_at    AS member_joined_at,
                
                    u.*
                
                FROM teams t
                
                LEFT JOIN team_members tm
                       ON t.id = tm.team_id
                
                LEFT JOIN users u
                       ON tm.user_id = u.user_id
                
                WHERE t.id = %s
                
                ORDER BY
                    t.id,
                    CASE tm.status
                        WHEN 'LEADER'  THEN 1
                        WHEN 'OFFICER' THEN 2
                        WHEN 'MEMBER'  THEN 3
                        ELSE 4
                    END,
                    u.user_name;
                """
                try:
                    await cursor.execute(query, (team_id, ))
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.get_team_by_id(team_id)
                except Exception as e:
                    logger.warning(f"Error getting team: {e}")
                    return None

                team: Optional[Team] = None

                for row in await cursor.fetchall():
                    if team is None:
                        user = self._map_db_result_to_user(row[7:])
                        team = Team(id=row[0], name=row[1], abbreviation=row[2], join_password=row[3], members={user: TeamStatus.from_str(row[5])})
                    elif team.id == row[0]:
                        team.members[self._map_db_result_to_user(row[7:])] = TeamStatus.from_str(row[5])

                return team

    async def create_team(self, team_name: str, team_abbr: str, join_password: str, leader: User) -> Team:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute(
                        "INSERT INTO teams (name, abbreviation, join_password) "
                        "VALUES (%s, %s, %s)", (team_name, team_abbr, join_password)
                    )
                    await conn.commit()
                    team_id = cursor.lastrowid
                    await cursor.execute(
                        "INSERT INTO team_members (team_id, user_id, status) VALUES (%s, %s, %s)",
                        (team_id, leader.user_id, TeamStatus.LEADER.name)
                    )
                    await conn.commit()
                    return await self.get_team_by_id(team_id)

                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.create_team(team_name, team_abbr, join_password)
                except aiomysql.IntegrityError as e:
                    logger.warning(f"Aborted duplication entry: {e}")
                    raise DuplicationError

    async def update_team(self, team: Team) -> Team:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute(
                        "UPDATE teams SET name = %s, abbreviation = %s, join_password = %s WHERE (id = %s)",
                        (team.name, team.abbreviation, team.join_password, team.id)
                    )
                    await conn.commit()
                    return await self.get_team_by_id(team.id)
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.update_team(team)
                except aiomysql.IntegrityError as e:
                    logger.warning(f"Aborted duplication entry: {e}")
                    raise DuplicationError


    async def add_member_to_team(self, team: Team, user: User, status: TeamStatus = TeamStatus.MEMBER) -> None:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute(
                        "INSERT INTO team_members (team_id, user_id, status) VALUES (%s, %s, %s)",
                        (team.id, user.user_id, status.name)
                    )
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.add_member_to_team(team, user, status)
                except aiomysql.IntegrityError as e:
                    logger.warning(f"Failed to add member {user.user_name} to team {team.name}: {e}")
                    raise DuplicationError


    async def remove_user_from_team(self, team: Team, user: User) -> None:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute(
                        "DELETE FROM team_members WHERE team_id = %s AND user_id = %s",
                        (team.id, user.user_id)
                    )
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.remove_user_from_team(team, user)

    async def change_tournament_status(self, tournament_id: int, status: TournamentStatus) -> None:
        async with self._connection_pool.acquire() as conn:
            async with conn.cursor(aiomysql.Cursor) as cursor:
                try:
                    await cursor.execute(
                        "UPDATE tournaments SET status = %s WHERE (id = %s)",
                        (status.name, tournament_id)
                    )
                    await conn.commit()
                except aiomysql.InterfaceError:
                    pool_init_result = await self.init_db_pool()
                    if not pool_init_result:
                        raise NoDatabaseConnectionError
                    return await self.change_tournament_status(tournament_id, status)
