import datetime

from peewee import *

import resources.Environment as Env
from src.model.BaseModel import BaseModel
from src.model.Crew import Crew
from src.model.User import User
from src.model.enums.GameStatus import GameStatus
from src.model.enums.income_tax.IncomeTaxEventType import IncomeTaxEventType
from src.model.game.GameOutcome import GameOutcome
from src.service.date_service import datetime_is_after, get_remaining_duration
from src.utils.string_utils import get_belly_formatted


class DavyBackFight(BaseModel):
    """
    DavyBackFight class
    """

    challenger_crew: Crew | ForeignKeyField = ForeignKeyField(
        Crew, backref="davy_back_fights_challengers"
    )
    opponent_crew: Crew | ForeignKeyField = ForeignKeyField(
        Crew, backref="davy_back_fights_opponents"
    )
    challenger_chest: int | BigIntegerField = BigIntegerField(default=0)
    opponent_chest: int | BigIntegerField = BigIntegerField(default=0)
    participants_count: int | IntegerField = IntegerField()
    duration_hours: int | IntegerField = IntegerField()
    penalty_days = IntegerField()
    status: int | SmallIntegerField = SmallIntegerField(
        default=GameStatus.AWAITING_OPPONENT_CONFIRMATION
    )
    start_date: datetime.datetime | DateTimeField = DateTimeField(null=True)
    end_date: datetime.datetime | DateTimeField = DateTimeField(null=True)
    conscript: User | ForeignKeyField = ForeignKeyField(
        User, backref="davy_back_fights_conscripts", null=True
    )
    conscript_date: datetime.datetime | DateTimeField = DateTimeField(null=True)
    penalty_end_date: datetime.datetime | DateTimeField = DateTimeField(null=True)
    penalty_payout: int | BigIntegerField = BigIntegerField(default=0)
    is_auto_accepted: bool | BooleanField = BooleanField(default=False)

    # Backlinks
    davy_back_fight_participants = None

    class Meta:
        db_table = "davy_back_fight"

    @staticmethod
    async def delete_expired_requests():
        """
        Delete expire requests, so requests that are in AWAITING_OPPONENT_CONFIRMATION status and
        were created more than x minutes ago
        """
        DavyBackFight.delete().where(
            DavyBackFight.status == GameStatus.AWAITING_OPPONENT_CONFIRMATION,
            DavyBackFight.date
            < (
                datetime.datetime.now()
                - datetime.timedelta(minutes=Env.DAVY_BACK_FIGHT_REQUEST_EXPIRATION_TIME.get_int())
            ),
        ).execute()

    def get_participants(self, crew: Crew = None) -> list[any]:
        """
        Get all the participants of a Davy Back Fight
        :param crew: The crew object
        :return: The list of participants
        """

        if crew is None:
            return self.davy_back_fight_participants

        return [p for p in self.davy_back_fight_participants if p.crew == crew]

    def get_participants_users(self, crew: Crew = None) -> list[User]:
        """
        Get all the participants of a Davy Back Fight
        :param crew: The crew object
        :return: The list of participants
        """

        if crew is None:
            return [p.user for p in self.davy_back_fight_participants]

        return [p.user for p in self.davy_back_fight_participants if p.crew == crew]

    def get_non_participants_users(self, crew: Crew) -> list[any]:
        """
        Get all the non-participants of a Davy Back Fight
        :param crew: The crew object
        :return: The list of non-participants
        """
        return [m for m in crew.get_members() if m not in self.get_participants_users(crew=crew)]

    def get_status(self) -> GameStatus:
        """
        Get the status of the Davy Back Fight
        :return: The status
        """

        return GameStatus(self.status)

    def in_progress(self) -> bool:
        """
        Check if the game is in progress
        :return: True if the game is in progress, False otherwise
        """
        return self.get_status() is GameStatus.IN_PROGRESS

    def has_ended(self) -> bool:
        """
        Check if the game has ended
        :return: True if the game has ended, False otherwise
        """
        return self.get_status().is_finished()

    def get_crew_gain(self, crew: Crew) -> int:
        """
        Get the crew gain
        :param crew: The crew object
        :return: The crew gain
        """

        return sum([p.contribution for p in self.davy_back_fight_participants if p.crew == crew])

    def get_opponent_crew(self, crew: Crew):
        """
        Get the opponent crew
        :param crew: The crew object
        :return: The opponent crew
        """

        if crew == self.challenger_crew:
            return self.opponent_crew
        return self.challenger_crew

    def get_in_progress_display_status(self, crew: Crew) -> GameStatus:
        """
        Get the in progress display status [WINNING, LOSING, DRAW]
        :param crew: The crew object
        :return: The in progress display status
        """

        crew_total = self.get_crew_gain(crew)
        opponent_total = self.get_crew_gain(self.get_opponent_crew(crew))

        if crew_total > opponent_total:
            return GameStatus.WINNING

        if crew_total < opponent_total:
            return GameStatus.LOSING

        return GameStatus.DRAW

    def get_participant(self, user: User) -> any:
        """
        Get the participant
        :param user: The user object
        :return: The participant
        """
        from src.model.DavyBackFightParticipant import DavyBackFightParticipant

        return self.davy_back_fight_participants.where(
            DavyBackFightParticipant.user == user
        ).get_or_none()

    def is_participant(self, user: User) -> bool:
        """
        Check if the user is a participant
        :param user: The user object
        :return: True if the user is a participant, False otherwise
        """
        return self.get_participant(user) is not None

    @staticmethod
    def get_contribution_events() -> list[IncomeTaxEventType]:
        """
        Get the tax events that generate a DBF contribution
        :return: The contribution events
        """
        return [IncomeTaxEventType.GAME, IncomeTaxEventType.FIGHT, IncomeTaxEventType.PLUNDER]

    def get_chest_amount(self, crew: Crew) -> int:
        """
        Get the chest amount
        :param crew: The crew object
        :return: The chest amount
        """
        if crew == self.challenger_crew:
            return self.challenger_chest
        return self.opponent_chest

    def get_opponent_chest_amount(self, crew: Crew) -> int:
        """
        Get the chest amount
        :param crew: The crew object
        :return: The chest amount
        """

        return self.get_chest_amount(self.get_opponent_crew(crew))

    def get_winner_crew(self) -> Crew | None:
        """
        Get the winner
        :return: The winner
        """
        if self.get_status() not in [GameStatus.WON, GameStatus.LOST]:
            return None

        if self.get_status() is GameStatus.WON:
            return self.challenger_crew

        return self.opponent_crew

    def is_winner_crew(self, crew: Crew) -> bool:
        """
        Check if the crew is the winner
        :param crew: The crew object
        :return: True if the crew is the winner, False otherwise
        """
        return self.get_winner_crew() == crew

    def get_remaining_time(self) -> str:
        from src.service.date_service import get_remaining_duration

        return get_remaining_duration(self.end_date)

    def get_outcome(self) -> GameOutcome:
        """
        Get the outcome of the game
        :return: The outcome
        """
        from src.model.DavyBackFightParticipant import DavyBackFightParticipant

        if DavyBackFightParticipant.get_total_contributions(
            self, self.challenger_crew
        ) >= DavyBackFightParticipant.get_total_contributions(self, self.opponent_crew):
            return GameOutcome.CHALLENGER_WON

        return GameOutcome.OPPONENT_WON

    def get_penalty_payout(self):
        """
        Get the penalty payout
        :return: The penalty payout
        """
        if self.get_status() is GameStatus.WON:
            return self.challenger_chest + self.penalty_payout

        return self.opponent_chest + self.penalty_payout

    def get_penalty_payout_formatted(self):
        """
        Get the penalty payout formatted
        :return: The penalty payout formatted
        """
        return get_belly_formatted(self.get_penalty_payout())

    def in_penalty_period(self) -> bool:
        """
        Check if the game is in penalty period
        :return: True if the game is in penalty period, False otherwise
        """
        return datetime_is_after(self.penalty_end_date)

    def get_penalty_remaining_time(self) -> str:
        """
        Get the penalty remaining time
        :return: The penalty remaining time
        """

        return get_remaining_duration(self.penalty_end_date)

    @staticmethod
    def get_max_participants(challenger_crew: Crew, opponent_crew: Crew) -> int:
        """
        Get the max participants
        :param challenger_crew: The challenger crew
        :param opponent_crew: The opponent crew
        :return: The max participants
        """
        return min(challenger_crew.get_member_count(), opponent_crew.get_member_count())

    @staticmethod
    def get_active_status() -> list[GameStatus]:
        """
        Get the active status
        :return: The active status
        """
        return [GameStatus.COUNTDOWN_TO_START, GameStatus.IN_PROGRESS]

    @staticmethod
    def get_ended_status() -> list[GameStatus]:
        """
        Get the ended status
        :return: The ended status
        """
        return [GameStatus.WON, GameStatus.LOST]

    @staticmethod
    def get_active() -> list["DavyBackFight"]:
        """
        Get the active Davy Back Fights
        :return: The active Davy Back Fights
        """
        return DavyBackFight.select().where(
            DavyBackFight.status << DavyBackFight.get_active_status()
        )

    @staticmethod
    def get_crew_with_enough_members() -> list[Crew]:
        """
        Get the crews with enough members for Davy Back Fight
        :return: The crews with enough members for Davy Back Fight
        """
        return [
            c
            for c in Crew.select()
            if c.get_member_count() >= Env.DAVY_BACK_FIGHT_MIN_PARTICIPANTS.get_int()
        ]

    @staticmethod
    def get_crews_in_active() -> list[Crew]:
        """
        Returns the crew in active Davy Back Fight
        :return: The crew in active Davy Back Fight
        """

        active = DavyBackFight.get_active()
        return [d.challenger_crew for d in active] + [d.opponent_crew for d in active]

    @staticmethod
    def get_ended_with_penalty() -> list["DavyBackFight"]:
        """
        Get the ended Davy Back Fights with penalty still active
        :return: The ended Davy Back Fights with penalty
        """
        return DavyBackFight.select().where(
            DavyBackFight.status.in_(DavyBackFight.get_ended_status()),
            DavyBackFight.penalty_end_date > datetime.datetime.now(),
        )

    @staticmethod
    def get_crews_in_penalty() -> list[Crew]:
        """
        Returns the crew in Davy Back Fight penalty
        :return: The crew in Davy Back Fight penalty
        """

        ended_with_penalty = DavyBackFight.get_ended_with_penalty()
        return [d.challenger_crew for d in ended_with_penalty] + [
            d.opponent_crew for d in ended_with_penalty
        ]


DavyBackFight.create_table()
