from abc import abstractmethod
from typing import Any, Dict, Tuple

from class_registry import AutoRegister, ClassRegistry
from discord import Interaction, Member, User
from discord.abc import Messageable
from redbot.core import commands

type_registry: Dict['str', 'Type'] = ClassRegistry(attr_name='key', unique=True)

class Type(metaclass=AutoRegister(type_registry)):
    """This is a base class for moderation types.

    Attributes:
        key (str): The key to use for this type. This should be unique, as this is how the type is registered internally. Changing this key will break existing cases with this type.
        string (str): The string to display for this type.
        verb (str): The verb to use for this type.
        embed_desc (str): The string to use for embed descriptions.
        channel (bool): Whether this type targets channels or users. If this is `true` in a subclass, its overriden handler methods should be typed with `discord.abc.Messageable` instead of `discord.Member | discord.User`.
        removes_from_guild (bool): Whether this type's handler removes the target from the guild, or if the moderation is expected to occur whenever the user is not in the guild. This does not actually remove the target from the guild, the handler method is responsible for that.

    Properties:
        name (str): The string to display for this type. This is the same as the `string` attribute.
    """

    key = "type"
    string = "type"
    verb = "typed"
    embed_desc = "been "
    channel = False
    removes_from_guild = False

    @abstractmethod
    def void(self) -> Any:
        """This method should be overridden by any child classes. This is a placeholder to allow for automatic class registration."""
        raise NotImplementedError

    @property
    def name(self) -> str:
        """Alias for the `string` attribute."""
        return self.string

    def __str__(self) -> str:
        return self.string

    def __repr__(self) -> str:
        attrs = [
            ('key', self.key),
            ('channel', self.channel),
        ]
        joined = ' '.join(f'{key}={value!r}' for key, value in attrs)
        return f"<{self.__class__.__name__} {joined}>"

    @classmethod
    async def handler(cls, ctx: commands.Context, target: Member | User | Messageable, silent: bool, **kwargs) -> 'Type': # pylint: disable=unused-argument
        """This method should be overridden by any child classes, but should retain the same starting keyword arguments.

        Arguments:
            ctx (commands.Context): The context of the command.
            target (discord.Member | discord.User | discord.abc.Messageable): The target of the moderation.
            silent (bool): Whether details about the moderation should be DM'ed to the target of the moderation.
        """
        raise NotImplementedError

    @classmethod
    async def resolve_handler(cls, moderation, reason: str) -> Tuple[bool, str]: # pylint: disable=unused-argument
        """This method should be overridden by any resolvable child classes, but should retain the same keyword arguments.
        If your moderation type should not be resolvable, do not override this.

        Arguments:
            moderation (aurora.models.Moderation): The moderation to resolve.
            reason (str): The reason for resolving the moderation.
        """
        raise NotImplementedError

    @classmethod
    async def expiry_handler(cls, moderation) -> int: # pylint: disable=unused-argument
        """This method should be overridden by any expirable child classes, but should retain the same keyword arguments and return an integer.
        If your moderation type should not expire, do not override this, but also do not set an `end_timestamp` when you log your moderation.

        Arguments:
            moderation (aurora.models.Moderation): The moderation that is expiring.
        """
        raise NotImplementedError

    @classmethod
    async def duration_edit_handler(cls, interaction: Interaction, old_moderation, new_moderation) -> bool: # pylint: disable=unused-argument
        """This method should be overridden by any child classes with editable durations, but should retain the same keyword arguments and should return True if the duration was successfully modified, or False if it was not.
        If your moderation type's duration should not be editable, do not override this.

        Arguments:
            interaction (discord.Interaction): The interaction that triggered the duration edit.
            old_moderation (aurora.models.Moderation): The old moderation, from before the `/edit` command was invoked.
            new_moderation (aurora.models.Moderation): The current state of the moderation.
        """
        raise NotImplementedError
