from string import ascii_letters, digits
from typing import Optional

from src.ezgg_lan_manager.services.DatabaseService import DatabaseService, DuplicationError
from src.ezgg_lan_manager.types.Team import TeamStatus, Team
from src.ezgg_lan_manager.types.User import User


class NameNotAllowedError(Exception):
    def __init__(self, disallowed_char: str) -> None:
        self.disallowed_char = disallowed_char


class AlreadyMemberError(Exception):
    def __init__(self) -> None:
        pass


class NotMemberError(Exception):
    def __init__(self) -> None:
        pass


class TeamLeadRemovalError(Exception):
    def __init__(self) -> None:
        pass


class TeamNameTooLongError(Exception):
    def __init__(self) -> None:
        pass


class TeamNameAlreadyTaken(Exception):
    def __init__(self) -> None:
        pass


class TeamAbbrInvalidError(Exception):
    def __init__(self) -> None:
        pass


class TeamService:
    ALLOWED_TEAM_NAME_SYMBOLS = ascii_letters + digits + "!#$%&*+,-./:;<=>?[]^_{|}~ "
    MAX_TEAM_NAME_LENGTH = 24
    MAX_TEAM_ABBR_LENGTH = 8

    def __init__(self, db_service: DatabaseService) -> None:
        self._db_service = db_service

    async def get_all_teams(self) -> list[Team]:
        return await self._db_service.get_teams()

    async def get_team_by_id(self, team_id: int) -> Optional[Team]:
        return await self._db_service.get_team_by_id(team_id)

    async def get_teams_for_user_by_id(self, user_id: int) -> list[Team]:
        all_teams = await self.get_all_teams()
        user_teams = []
        for team in all_teams:
            if user_id in [u.user_id for u in team.members.keys()]:
                user_teams.append(team)
        return user_teams

    async def create_team(self, team_name: str, team_abbr: str, join_password: str, leader: User) -> Team:
        disallowed_char = self._check_for_disallowed_char(team_name)
        if disallowed_char:
            raise NameNotAllowedError(disallowed_char)
        disallowed_char = self._check_for_disallowed_char(team_abbr)
        if disallowed_char:
            raise NameNotAllowedError(disallowed_char)

        if not team_name or len(team_name) > self.MAX_TEAM_NAME_LENGTH:
            raise TeamNameTooLongError()

        if not team_abbr or len(team_abbr) > self.MAX_TEAM_ABBR_LENGTH:
            raise TeamAbbrInvalidError()

        try:
            created_team = await self._db_service.create_team(team_name, team_abbr, join_password, leader)
        except DuplicationError:
            raise TeamNameAlreadyTaken
        return created_team

    async def update_team(self, team: Team) -> Team:
        """
        Updates the team EXCLUDING adding and removing members. This is to be done via add_member_to_team and remove_member_from_team
        :param team: New instance of Team that is to be updated
        :return: The modified Team instance
        """
        disallowed_char = self._check_for_disallowed_char(team.name)
        if disallowed_char:
            raise NameNotAllowedError(disallowed_char)
        disallowed_char = self._check_for_disallowed_char(team.abbreviation)
        if disallowed_char:
            raise NameNotAllowedError(disallowed_char)

        if not team.name or len(team.name) > self.MAX_TEAM_NAME_LENGTH:
            raise TeamNameTooLongError()

        if not team.abbreviation or len(team.abbreviation) > self.MAX_TEAM_ABBR_LENGTH:
            raise TeamAbbrInvalidError()

        return await self._db_service.update_team(team)

    async def add_member_to_team(self, team: Team, user: User, status: TeamStatus = TeamStatus.MEMBER) -> Team:
        if user in team.members:
            raise AlreadyMemberError()

        await self._db_service.add_member_to_team(team, user, status)
        return await self.get_team_by_id(team.id)

    async def remove_member_from_team(self, team: Team, user: User) -> Team:
        if user not in team.members:
            raise NotMemberError()

        if team.members[user] == TeamStatus.LEADER:
            raise TeamLeadRemovalError()

        await self._db_service.remove_user_from_team(team, user)
        return await self.get_team_by_id(team.id)

    async def is_join_password_valid(self, team_id: int, join_password: str) -> bool:
        team = await self.get_team_by_id(team_id)
        if not team:
            return False
        return team.join_password == join_password

    def _check_for_disallowed_char(self, name: str) -> Optional[str]:
        for c in name:
            if c not in self.ALLOWED_TEAM_NAME_SYMBOLS:
                return c
        return None
