import json
from pprint import pprint
from typing import Optional

from from_root import from_root

from src.ezgg_lan_manager.services.DatabaseService import DatabaseService
from src.ezgg_lan_manager.services.UserService import UserService
from src.ezgg_lan_manager.types.Participant import Participant
from src.ezgg_lan_manager.types.Team import Team
from src.ezgg_lan_manager.types.Tournament import Tournament
from src.ezgg_lan_manager.types.TournamentBase import ParticipantType, TournamentError
from src.ezgg_lan_manager.types.User import User


class TournamentService:
    def __init__(self, db_service: DatabaseService, user_service: UserService) -> None:
        self._db_service = db_service
        self._user_service = user_service

        # Crude cache mechanism. If performance suffers, maybe implement a queue with Single-Owner-Pattern or a Lock
        self._cache: dict[int, Tournament] = {}
        self._cache_dirty: bool = True  # Setting this flag invokes cache update on next read
        
    async def queue_cache_renewal(self) -> None:
        # Used in admin UI to provoke cache renewal after direct database access
        self._cache_dirty = True

    async def _update_cache(self) -> None:
        tournaments = await self._db_service.get_all_tournaments()
        for tournament in tournaments:
            self._cache[tournament.id] = tournament
        self._cache_dirty = False

    async def register_user_for_tournament(self, user_id: int, tournament_id: int) -> None:
        tournament = await self.get_tournament_by_id(tournament_id)
        if not tournament:
            raise TournamentError(f"No tournament with ID {tournament_id} was found")
        if tournament.participant_type != ParticipantType.PLAYER:
            raise TournamentError(f"Can only add single player to team tournament, not {tournament.participant_type.name}")
        participant = Participant(id_=user_id, participant_type=ParticipantType.PLAYER)
        tournament.add_participant(participant)
        await self._db_service.add_participant_to_tournament(participant, tournament)
        self._cache_dirty = True

    async def register_team_for_tournament(self, team_id: int, tournament_id: int) -> None:
        tournament = await self.get_tournament_by_id(tournament_id)
        if not tournament:
            raise TournamentError(f"No tournament with ID {tournament_id} was found")
        if tournament.participant_type != ParticipantType.TEAM:
            raise TournamentError(f"Can only add team to team tournament, not {tournament.participant_type.name}")
        participant = Participant(id_=team_id, participant_type=ParticipantType.TEAM)
        tournament.add_participant(participant)
        await self._db_service.add_participant_to_tournament(participant, tournament)
        self._cache_dirty = True

    async def unregister_user_from_tournament(self, user_id: int, tournament_id: int) -> None:
        tournament = await self.get_tournament_by_id(tournament_id)
        if not tournament:
            raise TournamentError(f"No tournament with ID {tournament_id} was found")
        participant = next(filter(lambda p: p.id == user_id, tournament.participants), None)
        if participant is not None:
            tournament.remove_participant(participant)
            await self._db_service.remove_participant_from_tournament(participant, tournament)
        self._cache_dirty = True

    async def unregister_team_from_tournament(self, team_id: int, tournament_id: int) -> None:
        tournament = await self.get_tournament_by_id(tournament_id)
        if not tournament:
            raise TournamentError(f"No tournament with ID {tournament_id} was found")
        participant = next(filter(lambda p: p.id == team_id, tournament.participants), None)
        if participant is not None:
            tournament.remove_participant(participant)
            await self._db_service.remove_participant_from_tournament(participant, tournament)
        self._cache_dirty = True

    async def get_tournaments(self) -> list[Tournament]:
        if self._cache_dirty:
            await self._update_cache()
        return list(self._cache.values())

    async def get_tournament_by_id(self, tournament_id: int) -> Optional[Tournament]:
        if self._cache_dirty:
            await self._update_cache()
        return self._cache.get(tournament_id, None)

    async def get_users_from_participant_list(self, participants: list[Participant]) -> list[User]:
        all_users = await self._db_service.get_all_users()
        participant_ids = [p.id for p in participants]
        return list(filter(lambda u: u.user_id in participant_ids, all_users))

    async def get_teams_from_participant_list(self, participants: list[Participant]) -> list[Team]:
        all_teams = await self._db_service.get_teams()
        participant_ids = [p.id for p in participants]
        return list(filter(lambda t: t.id in participant_ids, all_teams))

    async def start_tournament(self, tournament_id: int):
        tournament = await self.get_tournament_by_id(tournament_id)
        if tournament:
            tournament.start()
            await self._generate_initial_json_file(tournament)
            await self._db_service.change_tournament_status(tournament_id, tournament.status)
        self._cache_dirty = True

    async def cancel_tournament(self, tournament_id: int):
        tournament = await self.get_tournament_by_id(tournament_id)
        if tournament:
            tournament.cancel()
            await self._db_service.change_tournament_status(tournament_id, tournament.status)
        self._cache_dirty = True

    async def _generate_initial_json_file(self, tournament: Tournament) -> None:
        """
        Generates the initial JSON file for the tournament. Won't generate a new one if one already exists.
        ToDo: Remove this method when final tournament system is completed.
        """
        p = tournament.participants
        pairs = [
            (p[i], p[i + 1]) if i + 1 < len(p) else (p[i], None)
            for i in range(0, len(p), 2)
        ]
        data = {
            "rounds": [
                [
                    {
                        "opponent_1_id": pair[0].id if pair[0] is not None else None,
                        "opponent_2_id": pair[1].id if pair[1] is not None else None,
                        "winner": None
                    } for pair in pairs
                ]
            ]
        }

        # Resolve byes
        for match in data["rounds"][0]:
            if match["opponent_2_id"] is None:
                match["winner"] = match["opponent_1_id"]

        file_name = tournament.name.replace(" ", "_") + ".json"
        try:
            with open(from_root("tournament_data", file_name), "x") as f:
                json.dump(data, f, indent=4)
        except FileExistsError:
            pass
