import logging
from typing import Optional

from src.ezgg_lan_manager.services.AccountingService import AccountingService, InsufficientFundsError
from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
from src.ezgg_lan_manager.types.ConfigurationTypes import TicketInfo
from src.ezgg_lan_manager.types.Ticket import Ticket

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


class TicketNotAvailableError(Exception):
    def __init__(self, category: str):
        self.category = category


class UserAlreadyHasTicketError(Exception):
    pass


class TicketingService:
    def __init__(self, ticket_infos: tuple[TicketInfo, ...], db_service: DatabaseService,
                 accounting_service: AccountingService) -> None:
        self._ticket_infos = ticket_infos
        self._db_service = db_service
        self._accounting_service = accounting_service

    def get_ticket_info_by_category(self, category: str) -> Optional[TicketInfo]:
        return next(filter(lambda t: t.category == category, self._ticket_infos), None)

    def get_total_tickets(self) -> int:
        return sum([t_i.total_tickets for t_i in self._ticket_infos])

    async def get_available_tickets_for_category(self, category: str) -> int:
        ticket_info = self.get_ticket_info_by_category(category)
        if not ticket_info or ticket_info.total_tickets < 1:
            return 0
        result = ticket_info.total_tickets

        all_tickets = await self._db_service.get_tickets()
        for ticket in all_tickets:
            if ticket.category == category:
                result -= 1

        return result

    async def purchase_ticket(self, user_id: int, category: str) -> Ticket:
        all_categories = [t_i.category for t_i in self._ticket_infos]
        if category not in all_categories or (await self.get_available_tickets_for_category(category)) < 1:
            raise TicketNotAvailableError(category)

        user_balance = await self._accounting_service.get_balance(user_id)

        ticket_info = self.get_ticket_info_by_category(category)
        if not ticket_info:
            raise TicketNotAvailableError(category)

        if ticket_info.price > user_balance:
            raise InsufficientFundsError

        if await self.get_user_ticket(user_id):
            raise UserAlreadyHasTicketError

        if new_ticket := await self._db_service.generate_ticket_for_user(user_id, category):
            await self._accounting_service.remove_balance(
                user_id,
                ticket_info.price,
                f"TICKET {new_ticket.ticket_id}"
            )
            logger.debug(f"User {user_id} purchased ticket {new_ticket.ticket_id}")
            return new_ticket

        raise RuntimeError("An unknown error occurred while purchasing ticket")

    async def refund_ticket(self, user_id: int) -> bool:
        user_ticket = await self.get_user_ticket(user_id)
        if not user_ticket:
            return False

        ticket_info = self.get_ticket_info_by_category(user_ticket.category)
        if await self._db_service.delete_ticket(user_ticket.ticket_id):
            await self._accounting_service.add_balance(user_id, ticket_info.price,
                                                       f"TICKET REFUND {user_ticket.ticket_id}")
            logger.debug(f"User {user_id} refunded ticket {user_ticket.ticket_id}")
            return True

        return False

    async def transfer_ticket(self, ticket_id: int, user_id: int) -> bool:
        return await self._db_service.change_ticket_owner(ticket_id, user_id)

    async def get_user_ticket(self, user_id: int) -> Optional[Ticket]:
        return await self._db_service.get_ticket_for_user(user_id)
