from __future__ import annotations
from .message import Message
import time
from .user import User
from .webhook import Webhook
import asyncio
from ..utils import logging
from selfcord.models import message
from traceback import format_exception

log = logging.getLogger("Channel")

class Messageable:
    """Parent class specific for those classes that include a textchat for sending messages.
    """
    def __init__(self, http, bot) -> None:
        self.http = http
        self.bot = bot


    async def history(self) -> list[Message]:
        """
        Get channel message history.

        Args:
            No arguments required

        Returns:
            messages(list) : List of messages from the channel.
            None : If client does not have view permission for the channel or no data found
        """
        messages = []
        data = await self.http.request(method="get", endpoint=f"/channels/{self.id}/messages?limit=100")
        if data is None:
            return None

        for msg in data:
            messages.append(Message(msg, self.bot, self.http))
        while True:
            data = await self.http.request(method="get",
                                           endpoint=f"/channels/{self.id}/messages?limit=100&before={data[-1]['id']}")
            if len(data) > 0:
                for msg in data:
                    messages.append(Message(msg, self.bot, self.http))
            else:
                break

        return messages

    async def purge(self, amount: int = None) -> None:
        """
        Delete a number of messages, starting from the most recent.

        Args:
            amount(int) : Number of messages to purge/delete.

        Returns:
            No return value
        """
        messages = await self.history()
        msgs = []
        for msg in messages:
            if str(msg.author.id) == str(self.bot.user.id):
                msgs.append(msg)

        if amount != None:
            for i in range(0, len(msgs[:amount]), 3):
                await asyncio.gather(*(asyncio.create_task(message.delete()) for message in msgs[:amount][i:i + 3]))
        else:
            for i in range(0, len(msgs), 3):
                await asyncio.gather(*(asyncio.create_task(message.delete()) for message in msgs[i:i + 3]))

#            for i in range(0, len(msgs[:amount]), 2):
#                await asyncio.gather(*(asyncio.create_task(message.delete()) for message in msgs[:amount][i:i + 2]))
#                await asyncio.sleep(0.2)
#        else:
#            for i in range(0, len(msgs), 2):
#                await asyncio.gather(*(asyncio.create_task(message.delete()) for message in msgs[i:i + 2]))
#                await asyncio.sleep(0.2)



    async def spam(self, amount: int, content: str, tts=False) -> None:
        """
        Send multiple of the same message.

        Args:
            - amount(int) : Number of spam messages to send.
            - content(str) : The message to send.
            - tts(bool) = False : Specify whether it is a TTS message.

        Returns:
            No return value.
        """
        amount: list[int] = [i + 1 for i in range(amount)]
        for i in range(0, len(amount), 3):
            await asyncio.gather(
                *(asyncio.create_task(self.send(tts=tts, content=content)) for amoun in amount[i:i + 3]))
            await asyncio.sleep(0.3)

    async def send(self, content=None, tts=False) -> Message:
        """
        Send a message to the text channel.

        Args:
            - content(str) : Message content. Should be string type or similar. Discord `embed` type is not allowed.
            - tts(bool) : Specify whether message is text-to-speech or not

        Returns:
            No return value.
        """
        if hasattr(self, "guild_id"):
            resp = await self.http.request(method="post", endpoint=f"/channels/{self.id}/messages", headers={"origin": "https://discord.com", "referer": f"https://discord.com/channels/{self.guild_id}/{self.id}"},
                                    json={"content": content, "tts": tts})
            resp.update({"guild_id": self.guild_id})

        else:
            resp = await self.http.request(method="post", endpoint=f"/channels/{self.id}/messages", headers={"origin": "https://discord.com", "referer": f"https://discord.com/channels/{self.id}"},
                                    json={"content": content, "tts": tts})

        return Message(resp, self.bot, self.http)


    async def reply(self, message: str, content=None, tts=False) -> Message:
        """Reply to a specific message

        Args:
            message (str): Message to reply to
            content (_type_, optional): Message content to reply with. Defaults to None.
            tts (bool, optional): Specify whether message is text-to-speech or not. Defaults to False.

        Returns:
            No return value.
        """
        if hasattr(self, "guild_id"):
            resp = await self.http.request(method="post", endpoint=f"/channels/{self.id}/messages", headers={"origin": "https://discord.com", "referer": f"https://discord.com/channels/{self.guild_id}/{self.id}"},
                                    json={"content": content, "tts": tts,
                                        "message_reference": {"channel_id": f"{self.id}", "message_id": f"{message.id}"},
                                        "allowed_mentions": {"parse": ["users", "roles", "everyone"],
                                                            "replied_user": False}})
            resp.update({"guild_id": self.guild_id})
        else:
            resp = await self.http.request(method="post", endpoint=f"/channels/{self.id}/messages", headers={"origin": "https://discord.com", "referer": f"https://discord.com/channels/{self.id}"},
                                    json={"content": content, "tts": tts,
                                        "message_reference": {"channel_id": f"{self.id}", "message_id": f"{message.id}"},
                                        "allowed_mentions": {"parse": ["users", "roles", "everyone"],
                                                            "replied_user": False}})
        return Message(resp, self.bot, self.http)


class Voiceable(Messageable):
    """Parent class specific for those classes that include a voice chat, or call functionality
    """
    def __init__(self, http, bot) -> None:
        super().__init__(http, bot)
        self.http = http
        self.bot = bot

    async def video_call(self):
        """Initiates a video call on the specified channel
        """
        if hasattr(self, "guild_id"):
            await self.bot.gateway.video_call(self.id, self.guild_id)
        else:
            await self.bot.gateway.video_call(self.id)

    async def stream_call(self):
        """Initiates a stream call on the specified channel
        """
        if hasattr(self, "guild_id"):
            await self.bot.gateway.stream_call(self.id, self.guild_id)
        else:
            await self.bot.gateway.stream_call(self.id)

    async def call(self):
        """Initiates a call on the specified channel
        """

        if hasattr(self, "guild_id"):

            await self.bot.gateway.call(self.id, self.guild_id)
        else:
            await self.bot.gateway.call(self.id)


    async def leave_call(self):
        """Leaves call on the specified channel
        """
        await self.bot.gateway.leave_call()










class TextChannel(Messageable):
    """
    Text Channel Object
        Represents a Guild/Server channel within Discord.
        All methods are coroutines and thus need to be awaited.

    Returns:
        Name of the channel

    Functions:
        - delete()
        - edit(name[str], parent_id[int], position[int], topic[str])
        - history()
        - purge(amount[int])
        - spam(amount[int], content[str], tts[bool]=False)
        - send()
        - reply()
        - create_webhook()

    Example Object (ref: https://discord.com/developers/docs/resources/channel#channel-object-example-guild-text-channel)
        {
          "id": "41771983423143937",
          "guild_id": "41771983423143937",
          "name": "general",
          "type": 0,
          "position": 6,
          "permission_overwrites": [],
          "rate_limit_per_user": 2,
          "nsfw": true,
          "topic": "24/7 chat about how to gank Mike #2",
          "last_message_id": "155117677105512449",
          "parent_id": "399942396007890945",
          "default_auto_archive_duration": 60
        }
    """

    def __init__(self, data, bot, http) -> None:
        super().__init__(http, bot)
        self.permissions = []
        self.webhooks = []
        self.http = http
        self.bot = bot
        self._update(data)

    def __str__(self) -> str:
        return f"{self.name}"

    def _update(self, data: dict):
        """Updater method intended to create the attributes for the object

        Args:
            data (dict): JSON data from gateway
        """

        self.topic = data.get("topic")
        self.rate_limit_per_user = data.get("rate_limit_per_user")
        self.position = data.get("position")
        self.name = data.get("name")
        self.id = data.get("id")
        self.guild_id = data.get("guild_id")
        self.last_message_id = data.get("last_message_id")
        self.flags = data.get("flags")
        self.default_thread_rate_limit_per_user = data.get("default_thread_rate_limit_per_user")
        self.category_id = data.get("parent_id")

    async def delete(self):
        """
        Deletes the text channel object.

        Args:
            No arguments required

        Returns:
            No return value
        """
        await self.http.request(method="delete", endpoint=f"/channels/{self.id}")
        del self

    async def edit(self, name: str = None, parent_id: int = None, position: int = None, topic: str = None):
        """
        Edits the text channel object details. Requires the `Manage Channels` permission.
        Not all details can be modified.

        Args:
            - name(str) : Optional - Specifies a new name for the channel object. Defaults to None.
            - parent_id(int) : Optional - Specifies a new parent (category) for the text channel object.
            - position(int) : Optional - Modifies the sorting position of the text channel within the guild.
            - topic(str) : Optional - Modifies the channel topic (0-4096 characters for GUILD_FORUM channels,
                                        0-1024 characters for all others)
        Notice: Each parent category can contain up to 50 channels.

        Returns:
            e(str) : Exception parsed as string value.
        """
        payload = {}
        if name is not None:
            payload['name'] = name
        if parent_id is not None:
            payload['parent_id'] = parent_id
        if position is not None:
            payload['position'] = position
        if topic is not None and topic != "":
            payload['topic'] = topic

        try:
            await self.http.request(method="patch", endpoint=f"/channels/{self.id}", json=payload)
        except Exception as e:
            error = "".join(format_exception(e, e, e.__traceback__))
            log.error(f"Could not edit channel \n{error}")

    async def create_webhook(self, name: str = None, avatar_url: str = None) -> Webhook:
        """
        Creates a webhook in the specified channel

        Args:
            name (str, optional): Name of the webhook. Defaults to None.
            avatar_url (str, optional): Avatar of the webhook. Requires a URL. Defaults to None.

        Returns:
            webhook (Webhook): Returns the created webhook object.

        Raises:
            NameError: Name is required
        """
        fields = {}
        if name != None:
            fields['name'] = name
        else:
            log.error("Name is required")
        if avatar_url != None:
            data = await self.http.encode_image(avatar_url)
            fields['avatar'] = data
        data = await self.http.request(method="post", endpoint=f"/channels/{self.id}/webhooks", json=fields)
        webhook = Webhook(data, self.bot, http=self.http)
        self.webhooks.append(webhook)
        return webhook


class VoiceChannel(Voiceable, Messageable):
    """Voice Channel Object
    """

    def __init__(self, data, bot, http) -> None:
        super().__init__(http, bot)
        self.permissions = []
        self.webhooks = []
        self.http = http
        self.bot = bot
        self._update(data)

    def __str__(self) -> str:
        return f"{self.name}"

    def _update(self, data: dict):
        """Updater method intended to create the attributes for the object

        Args:
            data (dict): JSON data from gateway
        """
        self.name = data.get("name")
        self.id = data.get("id")
        self.guild_id = data.get("guild_id")
        self.last_message_id = data.get("last_message_id")
        self.rtc_region = data.get("rtc_region")
        self.flags = data.get("flags")
        self.bitrate = data.get("bitrate")
        self.rate_limit_per_user = data.get("rate_limit_per_user")
        self.position = data.get("position")
        self.category_id = data.get("parent_id")

    async def delete(self):
        """
        Deletes the voice channel object.
        """
        await self.http.request(method="delete", endpoint=f"/channels/{self.id}")
        del self

    async def create_webhook(self, name: str = None, avatar_url: str = None) -> Webhook :
        """
        Creates a webhook in the specified channel

        Args:
            name (str, optional): Name of the webhook. Defaults to None.
            avatar_url (str, optional): Avatar of the webhook. Requires a URL. Defaults to None.

        Returns:
            webhook (Webhook): Returns the created webhook object.

        Raises:
            NameError: Name is required
        """

        fields = {}
        if name != None:
            fields['name'] = name
        else:
            log.error("Name is required...")
        if avatar_url != None:
            data = await self.http.encode_image(avatar_url)
            fields['avatar'] = data
        data = await self.http.request(method="post", endpoint=f"/channels/{self.id}/webhooks", json=fields)
        webhook = Webhook(data, self.bot, self.http)
        self.webhooks.append(webhook)
        return webhook




class Category:
    """Category Object
    """

    def __init__(self, data, bot, http) -> None:
        self.bot = bot
        self.http = http
        self.permissions = []
        self._update(data)

    def __str__(self) -> str:
        return f"{self.name}"

    def _update(self, data: dict):
        """Updater method intended to create the attributes for the object

        Args:
            data (dict): JSON data from gateway
        """
        self.name = data.get("name")
        self.id = data.get("id")
        self.guild_id = data.get("guild_id")
        self.position = data.get("position")
        self.flags = data.get("flags")

    async def delete(self):
        """Deletes the Category object.
        """
        await self.http.request(method="delete", endpoint=f"/channels/{self.id}")
        del self


class DMChannel(Voiceable, Messageable):
    """DM Channel Object
    """

    def __init__(self, data, bot, http) -> None:
        super().__init__(http, bot)
        self.http = http
        self.bot = bot
        self._update(data)

    def __str__(self) -> str:
        return f"{self.recipient}"

    def _update(self, data: dict):
        """Updater method intended to create the attributes for the object

        Args:
            data (dict): JSON data from gateway
        """
        self.recipient = User(data.get("recipients")[0], self.bot, self.http)
        self.last_message_id = data.get("last_message_id")
        self.id = data.get("id")
        self.flags = data.get("id")

    async def delete(self):
        """Deletes the DM Channel object.
        """
        await self.http.request(method="delete", endpoint=f"/channels/{self.id}?silent=false")
        del self




class GroupChannel(Voiceable, Messageable):
    """Group Channel Object
    """

    def __init__(self, data, bot, http) -> None:
        super().__init__(http, bot)
        self.recipients = []
        self.http = http
        self.bot = bot
        self._update(data)

    def __str__(self) -> str:
        return f"{self.name}"

    def _update(self, data: dict):
        """Updater method intended to create the attributes for the object

        Args:
            data (dict): JSON data from gateway
        """
        for user in data.get("recipients"):
            self.recipients.append(User(user, self.bot, self.http))
        self.name = data.get("name")
        self.owner_id = data.get("owner_id")
        self.last_message_id = data.get("last_message_id")
        self.id = data.get("id")
        self.flags = data.get("flags")
        self.icon = data.get("icon")

    async def delete(self):
        """Deletes the Group Channel Object
        """
        await self.http.request(method="delete", endpoint=f"/channels/{self.id}?silent=true")
        del self


