import discord
from discord.ext import commands
from utils.sql_commands import DatabaseManager
from utils.bank_functions import *
from random import randint
from math import log2
from datetime import datetime
import io
from PIL import Image, ImageDraw, ImageFont
import logging
import os


def calculate_xp_needed_for_next_level(level: int) -> int:
    """Calculates the XP needed for the next level."""
    return int(35 * (level**2) + 35 * level)


class XP(commands.Cog):

    def __init__(self, client):
        self.client = client
        self.db = DatabaseManager("")

    @commands.Cog.listener()
    async def on_message(self, ctx: discord.Message) -> None:
        if ctx.author.bot:
            return

        try:
            data = self.db.fetch_one(
                "SELECT XP, LEVEL FROM users WHERE ID = %s", (ctx.author.id,)
            )
            if data is None:
                # Insert a new row for this user
                current_level = 0
                current_xp = 0
                self.db.execute_query(
                    "INSERT INTO users (ID, XP, LEVEL) VALUES (%s, %s, %s)",
                    (ctx.author.id, current_xp, current_level),
                )
            else:
                current_level = data.get("LEVEL", 0)
                current_xp = data.get("XP", 0)

            logging.info(f"XP: {current_xp}, Level: {current_level}")

            # Balanced XP calculation
            base = randint(5, 10)
            length_bonus = (
                min(len(ctx.content), 100) // 10
            )  # +1 XP per 10 chars, max +10
            extra_xp = base + length_bonus
            extra_xp = min(extra_xp, 20)  # cap at 20 XP per message
            current_xp += extra_xp

            if current_xp > calculate_xp_needed_for_next_level(current_level):
                current_level += 1
                player_balance = self.db.fetch_one(
                    "SELECT BANK FROM economy WHERE ID = %s", (ctx.author.id,)
                )
                player_balance = player_balance.get("BANK", 0) if player_balance else 0
                levelup_bonus = randint(500, 2000)
                new_balance = player_balance + levelup_bonus
                await update_money(ctx.author, bank=new_balance)

                await ctx.channel.send(
                    f"Congrats!! You achieved a new level.\nYou are now level {current_level}. You received {levelup_bonus}<:flooney:1194943899765051473>"
                )
                current_xp = 0

            # Use INSERT ... ON DUPLICATE KEY UPDATE for XP/level
            self.db.execute_query(
                "INSERT INTO users (ID, XP, LEVEL) VALUES (%s, %s, %s) "
                "ON DUPLICATE KEY UPDATE XP = %s, LEVEL = %s",
                (ctx.author.id, current_xp, current_level, current_xp, current_level),
            )
        except Exception as e:
            logging.error(f"Error in XP on_message: {e}")

    @commands.command()
    async def top(self, ctx: commands.Context) -> None:
        embed = discord.Embed(
            colour=discord.Colour.purple(),
            timestamp=ctx.message.created_at,
            title="Top Messengers",
        )
        users = self.db.fetch_all("SELECT ID, XP, LEVEL FROM users")
        if not users:
            return

        sorted_users = sorted(users, key=lambda x: int(x["XP"]), reverse=True)[:10]
        data = []

        for index, member in enumerate(sorted_users, start=1):
            try:
                member_obj = await self.client.fetch_user(int(member["ID"]))
                member_name = (
                    member_obj.display_name
                    if hasattr(member_obj, "display_name")
                    else str(member_obj)
                )
            except Exception:
                member_name = f"User {member['ID']}"
            member_xp, member_lvl = int(member["XP"]), int(member["LEVEL"])
            medals = ["🥇", "🥈", "🥉"]
            msg = f"**{medals[index - 1] if index <= 3 else index} `{member_name}` -- {member_xp:,}xp, {member_lvl:,}level**"
            data.append(msg)

        msg = "\n".join(data)
        embed.description = f"It's Based on XP of Global Users\n\n{msg}"
        guild_name = ctx.guild.name if ctx.guild is not None else "Direct Message"
        embed.set_footer(text=f"GLOBAL - {guild_name}")
        embed.color = discord.Color(0x00FF00)
        await ctx.reply(embed=embed, mention_author=False)

    @commands.command()
    async def stats(self, ctx: commands.Context) -> None:
        """Shows the user's current XP and level."""
        try:
            # Use a fallback background if the random one doesn't exist
            bg_path = f"images/image_{randint(1,100)}.jpg"
            if not os.path.exists(bg_path):
                bg_path = "images/default.jpg"
            profile_image = await self.create_profile_card(ctx.author, bg_path)

            await ctx.send(file=discord.File(profile_image, "profile.png"))

            if profile_image is not None:
                profile_image.close()

        except ZeroDivisionError:
            await ctx.send(f"{ctx.author.mention}, you haven't earned any XP yet.")
        except Exception as e:
            await ctx.send(f"Error creating profile card: {e}")
            logging.error(f"Error creating profile card: {e}")

    async def create_profile_card(
        self, user: discord.Member | discord.User, background_image_path: str
    ) -> io.BytesIO:
        """Create a profile card image for a user with a custom background and text overlay."""
        card_width, card_height = 400, 200
        text_color = (255, 255, 255)
        progress_bar_color = (0, 255, 0)
        outline_color = (255, 255, 255)
        tint_color = (50, 50, 50)
        transparency = 25
        opacity = int(255 * transparency / 100)

        # Load and resize the background image
        try:
            background_image = Image.open(background_image_path).resize(
                (card_width, card_height)
            )
        except Exception:
            # Fallback to a plain background if image fails
            # type: ignore[reportArgumentType] - Pillow expects a tuple for RGBA color, but Pylance incorrectly warns here
            background_image = Image.new(
                "RGBA", (card_width, card_height), (30, 30, 30, 255)  # type: ignore[reportArgumentType] - Pillow expects a tuple for RGBA color, but Pylance incorrectly warns here
            )

        card_image = Image.new("RGBA", (card_width, card_height))
        card_image.paste(background_image, (0, 0))

        overlay = Image.new(
            "RGBA",
            card_image.size,
            tint_color + (opacity,),  # type: ignore[reportArgumentType] - Pillow expects a tuple for RGBA color, but Pylance incorrectly warns here
        )
        card_image = Image.alpha_composite(card_image, overlay)

        draw = ImageDraw.Draw(card_image)

        # Load fonts with fallback
        font_path = "arial.ttf"
        try:
            font = ImageFont.truetype(font_path, 20)
        except Exception:
            font = ImageFont.load_default()

        draw.text((10, 10), f"User: {user.display_name}", fill=text_color, font=font)
        user_info = self.db.fetch_one(
            "SELECT XP, LEVEL FROM users WHERE ID = %s", (user.id,)
        )
        draw.text(
            (10, 40), f"Level: {user_info.get('LEVEL', 0)}", fill=text_color, font=font
        )
        draw.text(
            (10, 70),
            f"XP: {user_info.get('XP', 0)}/{calculate_xp_needed_for_next_level(user_info.get('LEVEL', 0))}",
            fill=text_color,
            font=font,
        )
        draw.text(
            (10, 100),
            f"Next level in: {calculate_xp_needed_for_next_level(user_info.get('LEVEL', 0))-user_info.get('XP', 0)}XP",
            fill=text_color,
            font=font,
        )

        max_xp = calculate_xp_needed_for_next_level(user_info.get("LEVEL", 0))
        progress_ratio = user_info.get("XP", 0) / max_xp if max_xp > 0 else 0
        progress_width = progress_ratio * 200

        draw.rectangle([(10, 130), (210, 150)], outline=outline_color, width=2)
        draw.rectangle([(10, 130), (10 + progress_width, 150)], fill=progress_bar_color)

        try:
            profile_size = 96
            margin = 30
            if user.avatar is not None:
                profile_picture_data = await user.avatar.read()
                profile_image = Image.open(io.BytesIO(profile_picture_data)).resize(
                    (profile_size, profile_size)
                )
            else:
                # Use a default avatar image if user has no avatar
                default_avatar_path = "images/default_avatar.png"
                if os.path.exists(default_avatar_path):
                    profile_image = Image.open(default_avatar_path).resize(
                        (profile_size, profile_size)
                    )
                else:
                    # Create a blank image if default not found
                    # type: ignore[reportArgumentType] - Pillow expects a tuple for RGBA color, but Pylance incorrectly warns here
                    profile_image = Image.new(
                        "RGBA", (profile_size, profile_size), (100, 100, 100, 255)  # type: ignore[reportArgumentType] - Pillow expects a tuple for RGBA color, but Pylance incorrectly warns here
                    )
            card_image.paste(
                profile_image,
                (card_width - profile_size - margin, margin),
                profile_image.convert("RGBA"),
            )
        except Exception as error:
            logging.error(f"Error fetching profile picture: {error}")

        image_bytes = io.BytesIO()
        card_image.save(image_bytes, format="PNG")
        image_bytes.seek(0)

        return image_bytes


async def setup(client: commands.Bot):
    await client.add_cog(XP(client))
