import datetime

from peewee import *

from src.model.BaseModel import BaseModel
from src.model.DevilFruit import DevilFruit
from src.model.GroupChat import GroupChat
from src.model.User import User
from src.model.enums.devil_fruit.DevilFruitCategory import DevilFruitCategory
from src.model.enums.devil_fruit.DevilFruitSource import DevilFruitSource
from src.model.enums.devil_fruit.DevilFruitTradeStatus import DevilFruitTradeStatus


class DevilFruitTrade(BaseModel):
    """
    Devil Fruit Trade class
    """

    devil_fruit: DevilFruit | ForeignKeyField = ForeignKeyField(
        DevilFruit,
        null=True,
        backref="devil_fruit_trades",
        on_delete="RESTRICT",
        on_update="CASCADE",
    )
    giver: User | ForeignKeyField = ForeignKeyField(
        User,
        null=True,
        backref="devil_fruit_trade_givers",
        on_delete="RESTRICT",
        on_update="CASCADE",
    )
    receiver: User | ForeignKeyField = ForeignKeyField(
        User,
        null=True,
        backref="devil_fruit_trade_receivers",
        on_delete="RESTRICT",
        on_update="CASCADE",
    )
    source: DevilFruitSource | SmallIntegerField = SmallIntegerField()
    price: int | BigIntegerField = BigIntegerField(null=True)
    tax_percentage: float | FloatField = FloatField(null=True)
    reason: str | CharField = CharField(max_length=100, null=True)
    date_sold: datetime.datetime | DateTimeField = DateTimeField(null=True)
    status: DevilFruitTradeStatus | SmallIntegerField = SmallIntegerField(
        default=DevilFruitTradeStatus.PENDING
    )
    group_chat: GroupChat | ForeignKeyField = ForeignKeyField(
        GroupChat,
        null=True,
        backref="devil_fruit_trade_group_chats",
        on_delete="RESTRICT",
        on_update="CASCADE",
    )
    message_id: int = IntegerField(null=True)

    class Meta:
        db_table = "devil_fruit_trade"

    @staticmethod
    def get_average_price(devil_fruit_category: DevilFruitCategory) -> int:
        """
        Get the average price of the sold fruits in a category
        :return: The average price
        """

        avg = (
            DevilFruitTrade.select(fn.AVG(DevilFruitTrade.price))
            .join(DevilFruit)
            .where(
                (DevilFruit.category == devil_fruit_category)
                & (DevilFruitTrade.price.is_null(False))
                & (DevilFruitTrade.status == DevilFruitTradeStatus.COMPLETED)
                & (DevilFruitTrade.price > 0)
            )
            .scalar()
        )

        return int(avg) if avg is not None else None

    def get_source(self) -> DevilFruitSource:
        """
        Get the source of the trade
        :return: The source
        """
        return DevilFruitSource(self.source)

    def get_status(self) -> DevilFruitTradeStatus:
        """
        Get the status of the trade
        :return: The status
        """
        return DevilFruitTradeStatus(self.status)

    @staticmethod
    def delete_pending_trades(devil_fruit: DevilFruit):
        """
        Delete pending trades of a devil fruit
        :param devil_fruit: The devil fruit
        :return: None
        """
        DevilFruitTrade.delete().where(
            (DevilFruitTrade.devil_fruit == devil_fruit)
            & (DevilFruitTrade.status == DevilFruitTradeStatus.PENDING)
        ).execute()

    @staticmethod
    def delete_pending_trades_in_shop(devil_fruit: DevilFruit):
        """
        Delete pending trades of a devil fruit in the shop
        :param devil_fruit: The devil fruit
        :return: None
        """
        DevilFruitTrade.delete().where(
            (DevilFruitTrade.devil_fruit == devil_fruit)
            & (DevilFruitTrade.status == DevilFruitTradeStatus.PENDING)
            & (DevilFruitTrade.source == DevilFruitSource.SHOP)
        ).execute()

    @staticmethod
    def delete_pending_trades_in_group(
        devil_fruit: DevilFruit, group_chat: GroupChat, exclude_id: int
    ):
        """
        Delete pending trades of a devil fruit in a group chat
        :param devil_fruit: The devil fruit
        :param group_chat: The group chat
        :param exclude_id: The id to exclude
        :return: None
        """
        DevilFruitTrade.delete().where(
            (DevilFruitTrade.devil_fruit == devil_fruit)
            & (DevilFruitTrade.status == DevilFruitTradeStatus.PENDING)
            & (DevilFruitTrade.group_chat == group_chat)
            & (DevilFruitTrade.id != exclude_id)
        ).execute()

    @staticmethod
    def get_pending_in_shop(devil_fruit: DevilFruit) -> "DevilFruitTrade":
        """
        Get a pending trade in the shop
        :param devil_fruit: The devil fruit
        :return: The trade
        """

        return DevilFruitTrade.get_or_none(
            DevilFruitTrade.devil_fruit == devil_fruit,
            DevilFruitTrade.status == DevilFruitTradeStatus.PENDING,
            DevilFruitTrade.source == DevilFruitSource.SHOP,
        )

    @staticmethod
    def get_all_selling_in_shop_or_group(
        group_chat: GroupChat = None, user: User = None
    ) -> list["DevilFruitTrade"]:
        """
        Get all trades in the shop
        :param group_chat: The group chat
        :param user: The user
        :return: The trades
        """
        return DevilFruitTrade.select().where(
            # Only non completed trades
            (DevilFruitTrade.devil_fruit.is_null(False))
            & (DevilFruitTrade.status == DevilFruitTradeStatus.PENDING)
            & (
                # Is in shop
                (DevilFruitTrade.source == DevilFruitSource.SHOP)
                | (
                    # Is in the given group chat
                    (DevilFruitTrade.source == DevilFruitSource.USER)
                    & (DevilFruitTrade.group_chat == group_chat)
                    # Not reserved to a specific user or reserved to the given user
                    & ((DevilFruitTrade.receiver.is_null()) | (DevilFruitTrade.receiver == user))
                )
            )
        )


DevilFruitTrade.create_table()
