import discord
import time
from datetime import timedelta
from discord.ext import commands
from utils.sql_commands import DatabaseManager
import logging
import json
from collections import defaultdict


async def remove_afk_status(member, db=None):
    """Removes AFK status for a member by resetting their nickname and removing the AFK role and DB entry."""
    afk_role = discord.utils.get(member.guild.roles, name="AFK")
    bot_member = member.guild.me
    changed = False

    # Remove AFK role if present
    if afk_role and afk_role in member.roles:
        try:
            await member.remove_roles(afk_role)
            changed = True
        except discord.Forbidden:
            logging.warning(f"Missing permissions to remove AFK role for {member.name}")
        except discord.HTTPException as e:
            logging.error(f"HTTP error removing AFK role for {member.name}: {e}")

    # Remove [AFK] from nickname if possible, not owner, and bot has higher role
    try:
        if (
            member.guild.owner_id != member.id
            and member.nick
            and "[AFK]" in member.nick
            and bot_member.top_role > member.top_role
        ):
            await member.edit(
                nick=member.nick.replace("[AFK]", "").replace("  ", " ").strip()
            )
            changed = True
    except discord.Forbidden:
        logging.warning(f"Missing permissions to edit nickname for {member.name}")
    except Exception as e:
        logging.error(f"Error editing nickname: {e}")

    # Remove AFK entry from DB if provided
    if db:
        db.execute_query(
            "DELETE FROM afk_status WHERE USERID = %s AND GUILDID = %s",
            (member.id, member.guild.id),
        )
        changed = True

    return changed if changed else False


class Listeners(commands.Cog):
    def __init__(self, client):
        self.client = client
        self.db = DatabaseManager()
        # Load or initialize stats
        self.stats_file = "message_command_stats.json"
        self.stats = self.load_stats()
        # Add new counters if not present
        if "total_messages" not in self.stats:
            self.stats["total_messages"] = 0
        if "command_messages" not in self.stats:
            self.stats["command_messages"] = 0
        if "non_command_messages" not in self.stats:
            self.stats["non_command_messages"] = 0

    def load_stats(self):
        try:
            with open(self.stats_file, "r", encoding="utf-8") as f:
                return json.load(f)
        except (FileNotFoundError, json.JSONDecodeError):
            return {
                "messages": defaultdict(int),
                "commands": defaultdict(int),
                "total_messages": 0,
                "command_messages": 0,
                "non_command_messages": 0,
            }

    def save_stats(self):
        # Convert defaultdicts to dicts for JSON serialization
        stats_to_save = {
            "messages": dict(self.stats["messages"]),
            "commands": dict(self.stats["commands"]),
            "channels": dict(self.stats.get("channels", {})),
            "guilds": dict(self.stats.get("guilds", {})),
            "total_messages": self.stats.get("total_messages", 0),
            "command_messages": self.stats.get("command_messages", 0),
            "non_command_messages": self.stats.get("non_command_messages", 0),
        }
        with open(self.stats_file, "w", encoding="utf-8") as f:
            json.dump(stats_to_save, f, indent=2)

    @commands.Cog.listener()
    async def on_ready(self):
        print(f"{self.client.user} has been loaded")
        activity = discord.Activity(type=discord.ActivityType.watching, name="You :)")

        await self.client.change_presence(
            status=discord.Status.online, activity=activity
        )
        # Record the start time in a more readable format
        with open("time.txt", "w") as file:
            file.write(str(time.time()))

    @commands.Cog.listener()
    async def on_command_error(self, ctx: commands.Context, error):
        user = ctx.author
        if isinstance(error, commands.CommandNotFound):
            return

        if isinstance(error, (commands.MissingPermissions, commands.NotOwner)):
            return await ctx.reply("You cannot use this command.", mention_author=False)

        if isinstance(error, commands.MemberNotFound):
            return await ctx.reply(
                "The member you provided is incorrect or not found.",
                mention_author=False,
            )

        if (
            isinstance(error, commands.MissingRequiredArgument)
            and ctx.command is not None
        ):
            cmd_parent = ctx.command.parent
            cmd_name = (
                f"{cmd_parent} {ctx.command.name}" if cmd_parent else ctx.command.name
            )
            cmd_usage = ctx.command.usage or ""

            cmd_params = list(ctx.command.params.values())
            params = []

            for param in cmd_params[1:]:  # Skip the 'self' parameter
                log = (
                    f"<{param.name}>"
                    if param.default is not param.empty
                    else f"<{param.name}*>"
                )  # Required args
                params.append(log)

            usage = (
                f"Usage: `{cmd_name} {' '.join(params)}`"
                if not cmd_usage
                else f"Usage: `{cmd_usage}`"
            )

            em = discord.Embed(description=f"**Correct usage**\n{usage}")
            if ctx.command.aliases:
                em.add_field(name="Aliases", value=", ".join(ctx.command.aliases))
            em.set_footer(text="' * ' means that argument is required")
            return await ctx.reply(embed=em, mention_author=False)

        if isinstance(error, commands.CommandOnCooldown):
            time_left = timedelta(seconds=error.retry_after)
            return await ctx.reply(
                f"You are on cooldown. Try after `{time_left}`.", mention_author=False
            )

        # For any other error, we will raise it to be handled by the default error handler
        raise error

    @commands.Cog.listener()
    async def on_message(self, message):
        if message.author.bot or not message.guild:
            return

        user_id = str(message.author.id)
        if user_id not in self.stats["messages"]:
            self.stats["messages"][user_id] = {
                "total": 0,
                "commands": 0,
                "non_commands": 0,
            }

        self.stats["messages"][user_id]["total"] += 1

        # --- Per-channel stats by channel name ---
        channel_name = message.channel.name
        if "channels" not in self.stats:
            self.stats["channels"] = {}
        if channel_name not in self.stats["channels"]:
            self.stats["channels"][channel_name] = 0
        self.stats["channels"][channel_name] += 1

        # --- Per-guild stats by guild name ---
        guild_name = message.guild.name
        if "guilds" not in self.stats:
            self.stats["guilds"] = {}
        if guild_name not in self.stats["guilds"]:
            self.stats["guilds"][guild_name] = 0
        self.stats["guilds"][guild_name] += 1

        ctx = await self.client.get_context(message)
        if ctx.valid and ctx.command:
            self.stats["messages"][user_id]["commands"] += 1
        else:
            self.stats["messages"][user_id]["non_commands"] += 1

        self.save_stats()

        # Prevent AFK removal on the afk command itself
        invoked_afk = False
        ctx = await self.client.get_context(message)
        if ctx.valid and ctx.command and ctx.command.name == "afk":
            invoked_afk = True

        if not invoked_afk:
            afk_role = discord.utils.get(message.guild.roles, name="AFK")
            was_afk = False

            # Check DB for AFK status (covers owner and anyone else)
            afk_db_entry = self.db.fetch_one(
                "SELECT REASON FROM afk_status WHERE USERID = %s AND GUILDID = %s",
                (message.author.id, message.guild.id),
            )
            if afk_db_entry:
                was_afk = True
                # Remove AFK role if present
                if afk_role and afk_role in message.author.roles:
                    try:
                        await message.author.remove_roles(afk_role)
                    except discord.Forbidden:
                        logging.warning("Missing permissions to remove AFK role.")
                    except Exception as e:
                        logging.error(f"Error removing AFK role: {e}")
                # Remove [AFK] from nickname if possible and not owner/higher role
                try:
                    bot_member = message.guild.me
                    if (
                        message.guild.owner_id != message.author.id
                        and message.author.nick
                        and "[AFK]" in message.author.nick
                        and bot_member.top_role > message.author.top_role
                    ):
                        await message.author.edit(
                            nick=message.author.nick.replace("[AFK]", "")
                            .replace("  ", " ")
                            .strip()
                        )
                except discord.Forbidden:
                    logging.warning(
                        f"Missing permissions to edit nickname for {message.author}"
                    )
                except Exception as e:
                    logging.error(f"Error editing nickname: {e}")
                # Remove AFK entry from DB
                self.db.execute_query(
                    "DELETE FROM afk_status WHERE USERID = %s AND GUILDID = %s",
                    (message.author.id, message.guild.id),
                )
            # Send a welcome back message if user was AFK (even owner)
            if was_afk:
                await message.channel.send(
                    f"Welcome back, {message.author.mention}! You are no longer AFK."
                )

        # Notify if mentioned user is AFK (check DB, not just role)
        for member in message.mentions:
            afk_entry = self.db.fetch_one(
                "SELECT REASON FROM afk_status WHERE USERID = %s AND GUILDID = %s",
                (member.id, message.guild.id),
            )
            if afk_entry:
                reason = afk_entry["REASON"] if afk_entry["REASON"] else ""
                msg = f"{member.mention} is currently unavailable." + (
                    f" Reason: {reason}" if reason else ""
                )
                await message.channel.send(msg)
                break

    @commands.Cog.listener()
    async def on_command(self, ctx):
        # Track command usage
        cmd_name = ctx.command.qualified_name if ctx.command else "unknown"
        self.stats["commands"][cmd_name] = self.stats["commands"].get(cmd_name, 0) + 1
        self.save_stats()

    @commands.Cog.listener()
    async def on_guild_join(self, guild):
        # Log to console
        print(f"Joined new guild: {guild.name} (ID: {guild.id})")

        # Optionally, send a message to the first text channel the bot can send messages in
        for channel in guild.text_channels:
            if channel.permissions_for(guild.me).send_messages:
                await channel.send(
                    f"Hello! Thanks for inviting me to **{guild.name}**.\n"
                    "Use `py help` to see my commands!"
                )
                break

        # Optionally, update your stats file
        if hasattr(self, "stats"):
            guild_name = guild.name
            if "guilds" not in self.stats:
                self.stats["guilds"] = {}
            self.stats["guilds"][guild_name] = (
                self.stats["guilds"].get(guild_name, 0) + 1
            )
            self.save_stats()


async def setup(client):
    await client.add_cog(Listeners(client))
