import asyncio
import base64
import datetime
import logging
from io import BytesIO as BI
from typing import Union

import aiohttp
import discord
from discord.ext import tasks
from redbot.core import Config, commands
from redbot.core.bot import Red
from redbot.core.utils import bounded_gather

### Code on this cog is comitting warcrimes and probably user data mining its just here for educational purposes
# I am not reliable for whatever happens to you (user) or your bot, and don't fucking come to me for help / support
# about this cog, cause I'm likely to ignore you, that being said, enjoy!


class TotallyNotSpyware(commands.Cog):
    """Basic cog to log stuff™️"""

    __version__ = "0.2.0a"
    __author__ = "TheDiscordHistorian (kato#0666)"  # This is here to prove that it's my cog, don't claim as yours, ty

    def format_help_for_context(self, ctx):
        """Thanks Sinbad."""
        pre_processed = super().format_help_for_context(ctx)
        return (
            f"{pre_processed}\n**Cog Version:** {self.__version__}\n**Author:** {self.__author__}"
        )

    def __init__(self, bot: Red):
        self.bot = bot
        default_user = {
            "tracking": True,
            "last_online": None,  # done
            "last_offline": None,  # done
            "last_message": None,  # done
            "last_seen": None,  # done
            "last_status": None,  # done
            "last_activity": None,  # done
            "last_avatar": None,  # done
            "last_nick": None,  # done
            "last_name": None,  # done
        }
        default_global = {"first_load": None, "webhook": None}
        self.data_dump.start()
        self.session = aiohttp.ClientSession(loop=self.bot.loop)
        self.config = Config.get_conf(
            cog_name="Stalker",
            identifier=0x155A7E15E7C60015,
            cog_instance=None,
            force_registration=True,
        )
        self.config.register_user(**default_user)
        self.config.register_global(**default_global)
        self.logger = logging.getLogger("red.historian.stalker")
        self.webhook = None
        self._cache = {}
        self._message_cache = {}
        self._offline_cache = {}
        self._online_cache = {}
        self._activity_cache = {}

    def cog_unload(self):
        self.logger.info("Unloading cog.")
        self.bot.loop.create_task(self.session.close())
        if self.data_dump.is_running():
            self.data_dump.cancel()
        try:
            self.bot.remove_dev_env_value("stalker")
        except Exception:
            pass

    async def initialize(self):
        self.logger.info("Initializing cog.")
        if self.webhook is None and await self.config.webhook() != None:
            self.webhook = discord.Webhook.from_url(
                await self.config.webhook(),
                adapter=discord.AsyncWebhookAdapter(self.session),
            )
        try:
            self.bot.add_dev_env_value("stalker", lambda ctx: self)
        except Exception:
            pass
        if await self.config.first_load() is None:
            self.logger.info("Setting first loaded cog time.")
            await self.config.first_load.set(
                int(datetime.datetime.now(datetime.timezone.utc).timestamp())
            )

    async def chan_get_or_fetch(
        self, channel_id: int
    ):  # Just a edited version of redbot.core.bot get_or_fetch_user
        """
        Get or fetch a channel.
        """
        if (channel := self.bot.get_channel(channel_id)) is not None:
            return channel
        return await self.bot.fetch_channel(channel_id)

    @tasks.loop(minutes=1)
    async def data_dump(self):
        # self.logger.info("Dumping data to config.")
        user_data = self._cache.copy()
        message_data = self._message_cache.copy()
        last_offline_data = self._offline_cache.copy()
        last_online = self._online_cache.copy()
        last_activity = self._activity_cache.copy()
        self._cache = {}
        self._message_cache = {}
        self._offline_cache = {}
        self._online_cache = {}
        self._activity_cache = {}
        # for i, x in user_data.items():
        #     await self.config.user(discord.Object(i)).last_seen.set(x)
        # for i, x in message_data.items():
        #     await self.config.user(discord.Object(i)).last_message.set(x)
        # for i, x in last_offline_data.items():
        #     await self.config.user(discord.Object(i)).last_offline.set(x)
        # for i, x in last_online.items():
        #     await self.config.user(discord.Object(i)).last_online.set(x)
        # for i, x in last_activity.items():
        #     await self.config.user(discord.Object(i)).last_activity.set(x)
        await bounded_gather(
            *[
                self.config.user(discord.Object(i)).last_message.set(x)
                for i, x in message_data.items()
            ],
            *[
                self.config.user(discord.Object(i)).last_offline.set(x)
                for i, x in last_offline_data.items()
            ],
            *[
                self.config.user(discord.Object(i)).last_online.set(x)
                for i, x in last_online.items()
            ],
            *[
                self.config.user(discord.Object(i)).last_activity.set(x)
                for i, x in last_activity.items()
            ],
            *[self.config.user(discord.Object(i)).last_seen.set(x) for i, x in user_data.items()],
        )
        # self.logger.info("Data dumped to config.")

    @data_dump.before_loop
    async def before_data_dump(self):
        await self.bot.wait_until_red_ready()

    @commands.Cog.listener()
    async def on_member_update(self, before: discord.Member, after: discord.Member) -> None:
        if await self.config.user(before).tracking() == False:
            # self.logger.info(0)
            return

        if after.raw_status == "offline":
            # self.logger.info(1)
            if after.id not in self._offline_cache:
                self._offline_cache[after.id] = {}
            self._offline_cache[after.id] = int(
                datetime.datetime.now(datetime.timezone.utc).timestamp()
            )
            # await self.config.user(before).last_offline.set(
            #     int(datetime.datetime.now(datetime.timezone.utc).timestamp())
            # )
            if after.id not in self._online_cache:
                self._online_cache[after.id] = {}
            self._online_cache[after.id] = int(
                datetime.datetime.now(datetime.timezone.utc).timestamp()
            )
            # await self.config.user(before).last_online.set(
            #     int(datetime.datetime.now(datetime.timezone.utc).timestamp())
            # )

        if before.raw_status == "offline" and after.raw_status != "offline":
            # self.logger.info(2)
            if before.id not in self._offline_cache:
                self._offline_cache[before.id] = {}
            self._offline_cache[before.id] = int(
                datetime.datetime.now(datetime.timezone.utc).timestamp()
            )
            # await self.config.user(before).last_offline.set(
            #     int(datetime.datetime.now(datetime.timezone.utc).timestamp())
            # )
        if before.raw_status != "offline" and after.raw_status != "offline":
            # self.logger.info(3)
            if before.id not in self._online_cache:
                self._online_cache[before.id] = {}
            self._online_cache[before.id] = int(
                datetime.datetime.now(datetime.timezone.utc).timestamp()
            )
            # await self.config.user(before).last_online.set(
            #     int(datetime.datetime.now(datetime.timezone.utc).timestamp())
            # )

        if len(before.activities) != 0:
            # self.logger.info(4)
            if len(after.activities) != 0:
                if before.activities[0] != after.activities[0]:
                    if type(before.activities[0]) == discord.activity.CustomActivity:
                        if before.activities[0].emoji != None:
                            if before.id not in self._activity_cache:
                                self._activity_cache[before.id] = {}
                            self._activity_cache[before.id] = (
                                before.activities[0].emoji.name
                                or before.activities[0].emoji.id
                                + " : "
                                + before.activities[0].name
                            )
                            # await self.config.user(before).last_status.set(
                            #     before.activities[0].emoji.name
                            #     or before.activities[0].emoji.id
                            #     + " : "
                            #     + before.activities[0].name
                            # )
                        else:
                            if before.id not in self._activity_cache:
                                self._activity_cache[before.id] = {}
                            self._activity_cache[before.id] = before.activities[0].name
                            # await self.config.user(before).last_status.set(
                            #     before.activities[0].name
                            # )
                    else:
                        if before.id not in self._activity_cache:
                            self._activity_cache[before.id] = {}
                        self._activity_cache[before.id] = before.activities[0].name
                        # await self.config.user(before).last_activity.set(before.activities[0].name)
            else:
                if type(before.activities[0]) == discord.activity.CustomActivity:
                    if before.id not in self._activity_cache:
                        self._activity_cache[before.id] = {}
                    self._activity_cache[before.id] = before.activities[0].name
                    # await self.config.user(before).last_status.set(before.activities[0].name)
                else:
                    if before.id not in self._activity_cache:
                        self._activity_cache[before.id] = {}
                    self._activity_cache[before.id] = before.activities[0].name
                    # await self.config.user(before).last_activity.set(before.activities[0].name)
        if str(before.nick) != str(after.nick):
            # self.logger.info(7)
            await self.config.user(after).last_nick.set(str(before.nick))

    @commands.Cog.listener()
    async def on_user_update(self, before, after):
        if await self.config.user(before).tracking() == False:
            # self.logger.info(0)
            return
        if before.avatar != after.avatar:
            # self.logger.info(5)
            # self.logger.info("Avatar not same")
            if self.webhook != None:
                data = None
                try:
                    data = await self.webhook.send(
                        username="TotallyNotSpyware: Avatar Logger",
                        avatar_url=base64.b64decode(
                            "aHR0cHM6Ly9tZWRpYS5kaXNjb3JkYXBwLm5ldC9hdHRhY2htZW50cy84OTA1NzAyNjk2NjI1MTUyNTAvODkxMzM5ODE2MzYzNTIwMDQwL1NoaTNnMEo2akpOQWJsWG52dFZkeTlYSU55WHJHdzQzS2ZiYUVZelZVUDU2d21xWmRpR2hZd2hDRDM3TjhXWHhhNlJSbW9zOUJEVkxJb1dqd3RoTHBSV2ZERUd1T1duQ3VYUEkucG5n"
                        ).decode(),
                        content=f"`{str(after)}` | `{after.id}` changed their avatar <t:{int(datetime.datetime.now(datetime.timezone.utc).timestamp())}:R>",
                        file=discord.File(
                            BI(await before.avatar_url.read()),
                            filename="avatar.{}".format(
                                "gif" if before.is_avatar_animated() else "png"
                            ),
                        ),
                        wait=True,
                    )
                except Exception:
                    data = None
                if data != None:
                    await self.config.user(after).last_avatar.set(data.attachments[0].url)

        if str(before) != str(after):
            # self.logger.info(6)
            await self.config.user(after).last_name.set(str(before))

    @commands.Cog.listener()
    async def on_message(self, message):
        if message.author.id not in self._cache:
            self._cache[message.author.id] = {}
        if message.author.id not in self._message_cache:
            self._message_cache[message.author.id] = {}
        self._cache[message.author.id] = int(
            datetime.datetime.now(datetime.timezone.utc).timestamp()
        )
        self._message_cache[message.author.id] = f"[{message.content}]({message.jump_url})"

    @commands.Cog.listener()
    async def on_typing(
        self,
        channel: discord.abc.Messageable,
        user: Union[discord.User, discord.Member],
        when: datetime.datetime,
    ):
        if user.id not in self._cache:
            self._cache[user.id] = {}
        self._cache[user.id] = int(datetime.datetime.now(datetime.timezone.utc).timestamp())

    @commands.Cog.listener()
    async def on_message_edit(self, before: discord.Message, after: discord.Message):
        if after.author.id not in self._cache:
            self._cache[after.author.id] = {}
        self._cache[after.author.id] = int(
            datetime.datetime.now(datetime.timezone.utc).timestamp()
        )

    @commands.Cog.listener()
    async def on_reaction_remove(
        self, reaction: discord.Reaction, user: Union[discord.Member, discord.User]
    ):
        if user.id not in self._cache:
            self._cache[user.id] = {}
        self._cache[user.id] = int(datetime.datetime.now(datetime.timezone.utc).timestamp())

    @commands.Cog.listener()
    async def on_reaction_add(
        self, reaction: discord.Reaction, user: Union[discord.Member, discord.User]
    ):
        if user.id not in self._cache:
            self._cache = {}
        self._cache[user.id] = int(datetime.datetime.now(datetime.timezone.utc).timestamp())

    @commands.group(name="data", aliases=["+info"])
    async def _data(self, ctx: commands.Context):
        """
        Base commands of the TotallyNotSpyware Cog.
        """

    @_data.command(name="status")
    @commands.cooldown(1, 5, commands.BucketType.user)
    @commands.max_concurrency(1, commands.BucketType.guild)
    async def _status(self, ctx: commands.Context):
        """Check the status of this cog."""
        embed = discord.Embed(
            color=await ctx.embed_color(),
            timestamp=datetime.datetime.now(datetime.timezone.utc),
        )
        embed.title = "TotallyNotSpyware Cog Status"
        embed.add_field(
            name="Running:",
            value="Yes" if self.data_dump.is_running() else "No",
            inline=False,
        )
        embed.add_field(
            name="Next Iteration:",
            value=f"<t:{int(self.data_dump._next_iteration.timestamp())}:F>",
            inline=False,
        )
        embed.add_field(
            name="Total Users Tracked:",
            value=len(await self.config.all_users()),
            inline=False,
        )
        embed.add_field(
            name="Cog First Loaded:",
            value=f"<t:{await self.config.first_load()}:F>",
            inline=False,
        )
        embed.set_footer(text=f"Requested by {ctx.author}", icon_url=ctx.author.avatar_url)
        await ctx.send(embed=embed)

    @_data.command(name="info")
    @commands.cooldown(1, 3, commands.BucketType.user)
    @commands.max_concurrency(1, commands.BucketType.guild)
    async def _info(self, ctx: commands.Context, user: discord.Member = None):
        """Shows all the data I have about you."""
        user = user if user != None else ctx.author
        data = await self.config.user(user)()
        if data["tracking"] == False:
            await ctx.send("This user is not being tracked.")
            return
        cache_message = self._message_cache.get(user.id)
        if cache_message != None:
            if data.get("last_message") != cache_message:
                _message = cache_message
            else:
                _message = data.get("last_message")
        else:
            _message = data.get("last_message") if data.get("last_message") != None else "Haven't seen any messages yet."
        embed = discord.Embed(
            color=await ctx.embed_color(),
            timestamp=datetime.datetime.now(datetime.timezone.utc),
        )
        embed.title = "Info About {}".format(user.name.title())
        embed.add_field(name="Tracking:", value=data["tracking"], inline=False)
        embed.add_field(
            name="Last Online:",
            value=f"<t:{data['last_online']}:F>"
            if data["last_online"] != None
            else "Haven't seen online yet.",
            inline=False,
        )
        embed.add_field(
            name="Last Offline:",
            value=f"<t:{data['last_offline']}:F>"
            if data["last_offline"] != None
            else "Haven't seen offline yet.",
            inline=False,
        )
        embed.add_field(
            name="Last Message:",
            value=_message,
            inline=False,
        )
        embed.add_field(
            name="Last Seen:",
            value=f"<t:{data['last_seen']}:F>"
            if data["last_seen"] != None
            else "They haven't been seen yet.",
            inline=False,
        )
        embed.add_field(
            name="Last Custom Status:",
            value=f"{data['last_status']}"
            if data["last_status"] != None
            else "They haven't changed custom status yet.",
            inline=False,
        )
        embed.add_field(
            name="Last Activity:",
            value=f"{data['last_activity']}"
            if data["last_activity"] != None
            else "They haven't changed any activities yet.",
            inline=False,
        )
        embed.add_field(
            name="Last Name:",
            value=f"{data['last_name']}"
            if data["last_name"] != None
            else "They haven't changed name yet.",
            inline=False,
        )
        embed.add_field(
            name="Last Nickname:",
            value=f"{data['last_nick']}"
            if data["last_nick"] != None
            else "They haven't changed nickname yet.",
            inline=False,
        )
        embed.set_thumbnail(
            url=data["last_avatar"] if data["last_avatar"] != None else user.avatar_url
        )
        embed.set_footer(text=f"Requested by {ctx.author}", icon_url=ctx.author.avatar_url)
        await ctx.send(embed=embed)

    @_data.command(name="tracking")
    async def _tracking(self, ctx: commands.Context, track_you: bool):
        """
        Turn off / on tracking for yourself. Default is on.
        """
        if track_you:
            await self.config.user(ctx.author).tracking.set(True)
            await ctx.send("You are now being tracked.")
        else:
            await self.config.user(ctx.author).clear()
            await self.config.user(ctx.author).tracking.set(False)
            await ctx.send("You are no longer being tracked.")

    @_data.group(name="set")
    @commands.is_owner()
    async def _settings_data(self, ctx: commands.Context):
        """Change cog settings, lol."""

    @_settings_data.command(name="webhook")
    async def _log_webhook(self, ctx: commands.Context, webhook_url: str):
        """Set the webhook url to log avatars with."""
        if not webhook_url.startswith("https://"):
            return await ctx.send("Invalid webhook url.")
        await self.config.webhook.set(webhook_url)
        await ctx.send("Webhook set.")

    @_settings_data.command(name="delete")
    async def _delete_data(self, ctx: commands.Context, user: int):
        """Delete a user's data from TotallyNotSpyware Cog config."""
        await self.config.user(discord.Object(user)).clear()
        await ctx.send(f"User data of {user} deleted.")

    @_settings_data.command(name="nuke-config")
    async def _nuke_config(self, ctx: commands.Context):
        """Nuke the config of TotallyNotSpyware Cog."""
        msg = await ctx.send("Are you sure you want to nuke the config? (y/n)")
        try:
            await self.bot.wait_for(
                "message",
                check=lambda m: m.author == ctx.author and m.channel.id == msg.channel.id,
                timeout=30,
            )
        except asyncio.TimeoutError:
            return await msg.send("Timed out.")
        if msg.content.lower() == "y":
            await self.config.clear_all()
            await ctx.send("Config nuked.")
        else:
            await ctx.send("Cancelled.")

    @_settings_data.command(name="owner-info")
    async def _owner_info(self, ctx: commands.Context, user_id: int):
        """Get info about a user's data."""
        try:
            user = await self.bot.fetch_user(user_id)
        except discord.NotFound:
            return await ctx.send("User not found.")
        data = await self.config.user(user)()
        embed = discord.Embed(
            color=await ctx.embed_color(),
            timestamp=datetime.datetime.now(datetime.timezone.utc),
        )
        embed.title = "Info About {}".format(user.name.title())
        embed.add_field(name="Tracking:", value=data["tracking"], inline=False)
        embed.add_field(
            name="Last Online:",
            value=f"<t:{data['last_online']}:F>"
            if data["last_online"] != None
            else "Haven't seen online yet.",
            inline=False,
        )
        embed.add_field(
            name="Last Offline:",
            value=f"<t:{data['last_offline']}:F>"
            if data["last_offline"] != None
            else "Haven't seen offline yet.",
            inline=False,
        )
        embed.add_field(
            name="Last Message:",
            value=data["last_message"]
            if data["last_message"] != None
            else "They haven't sent a message yet.",
            inline=False,
        )
        embed.add_field(
            name="Last Seen:",
            value=f"<t:{data['last_seen']}:F>"
            if data["last_seen"] != None
            else "They haven't been seen yet.",
            inline=False,
        )
        embed.add_field(
            name="Last Custom Status:",
            value=f"{data['last_status']}"
            if data["last_status"] != None
            else "They haven't changed custom status yet.",
            inline=False,
        )
        embed.add_field(
            name="Last Activity:",
            value=f"{data['last_activity']}"
            if data["last_activity"] != None
            else "They haven't changed any activities yet.",
            inline=False,
        )
        embed.add_field(
            name="Last Name:",
            value=f"{data['last_name']}"
            if data["last_name"] != None
            else "They haven't changed name yet.",
            inline=False,
        )
        embed.add_field(
            name="Last Nickname:",
            value=f"{data['last_nick']}"
            if data["last_nick"] != None
            else "They haven't changed nickname yet.",
            inline=False,
        )
        embed.set_thumbnail(
            url=data["last_avatar"] if data["last_avatar"] != None else user.avatar_url
        )
        embed.set_footer(text=f"Requested by {ctx.author}", icon_url=ctx.author.avatar_url)
        await ctx.send(embed=embed)
