import discord
from discord.ext import commands, tasks
from utils.sql_commands import DatabaseManager
from utils.bank_functions import bank_data, update_money
from random import choice
from datetime import datetime, timedelta
import logging

TICKET_TYPES = {
    "standard": {"price": 500, "weight": 1},
    "premium": {"price": 2000, "weight": 5},
}
LOTTERY_INTERVAL_HOURS = 24  # Draw every 24 hours
MAX_TICKETS_PER_USER = 10
ROLLOVER_BONUS = 500


class Lottery(commands.Cog):
    def __init__(self, client):
        self.client = client
        self.db = DatabaseManager()
        self.lottery_draw.start()

    def cog_unload(self):
        self.lottery_draw.cancel()

    def get_jackpot(self) -> int:
        try:
            result = self.db.fetch_one("SELECT jackpot FROM lottery_state WHERE id = 1")
            return int(result["jackpot"]) if result else 0
        except Exception as e:
            logging.error(f"Error fetching jackpot: {e}")
            return 0

    def set_jackpot(self, amount: int) -> None:
        try:
            self.db.execute_query(
                "UPDATE lottery_state SET jackpot = %s WHERE id = 1", (amount,)
            )
        except Exception as e:
            logging.error(f"Error setting jackpot: {e}")

    def add_to_jackpot(self, amount: int) -> None:
        try:
            self.db.execute_query(
                "UPDATE lottery_state SET jackpot = jackpot + %s WHERE id = 1",
                (amount,),
            )
        except Exception as e:
            logging.error(f"Error adding to jackpot: {e}")

    def get_last_draw_time(self) -> datetime:
        try:
            result = self.db.fetch_one(
                "SELECT last_draw FROM lottery_draw_time WHERE id = 1"
            )
            last_draw = result["last_draw"] if result and result["last_draw"] else None
            if last_draw is None:
                # Set to now if missing and update DB
                now = datetime.utcnow()
                self.set_last_draw_time(now)
                return now
            if isinstance(last_draw, str):
                try:
                    last_draw = datetime.fromisoformat(last_draw)
                except Exception:
                    last_draw = datetime.strptime(last_draw, "%Y-%m-%d %H:%M:%S")
            return last_draw
        except Exception as e:
            logging.error(f"Error fetching last draw time: {e}")
            now = datetime.utcnow()
            self.set_last_draw_time(now)
            return now

    def set_last_draw_time(self, draw_time: datetime) -> None:
        try:
            self.db.execute_query(
                "UPDATE lottery_draw_time SET last_draw = %s WHERE id = 1", (draw_time,)
            )
        except Exception as e:
            logging.error(f"Error setting last draw time: {e}")

    def notify_lottery_result(self, user_id: int):
        try:
            result = self.db.fetch_one(
                "SELECT * FROM lottery_results WHERE WINNER_ID = %s AND CLAIMED = 0 ORDER BY DRAW_TIME DESC LIMIT 1",
                (user_id,),
            )
            if not result:
                return None
            # Remove the result after notifying
            self.db.execute_query(
                "DELETE FROM lottery_results WHERE ID = %s", (result["ID"],)
            )
            return (True, result["AMOUNT"])
        except Exception as e:
            logging.error(f"Error notifying lottery result: {e}")
            return None

    @commands.command(
        name="buyticket",
        help=f"Buy one or more lottery tickets for yourself or a group.",
    )
    async def buy_ticket(
        self,
        ctx: commands.Context,
        ticket_type: str = "standard",
        amount: int = 1,
        group_id: str | None = None,
        member: discord.Member | None = None,
    ):
        ticket_type = ticket_type.lower()
        if ticket_type not in TICKET_TYPES:
            await ctx.reply(
                f"Invalid ticket type. Choose from: {', '.join(TICKET_TYPES)}"
            )
            return
        if amount < 1 or amount > MAX_TICKETS_PER_USER:
            await ctx.reply(
                f"You can only buy between 1 and {MAX_TICKETS_PER_USER} tickets at once."
            )
            return
        user = member or ctx.author
        price = TICKET_TYPES[ticket_type]["price"] * amount
        try:
            # Notify about last draw if relevant (only if user is the winner)
            notify = self.notify_lottery_result(user.id)
            if notify:
                await ctx.reply(
                    f"🎉 You won the last lottery! Jackpot: {notify[1]:,} coins.",
                    mention_author=False,
                )

            user_data = await bank_data(user)
            wallet = int(user_data.get("WALLET", 0))
            if wallet < price:
                await ctx.reply(
                    f"You need at least {price} coins to buy {amount} {ticket_type} ticket(s).",
                    mention_author=False,
                )
                return

            if group_id:
                group_exists = self.db.fetch_one(
                    "SELECT 1 FROM lottery_groups WHERE group_id = %s", (group_id,)
                )
                in_group = self.db.fetch_one(
                    "SELECT 1 FROM lottery_group_members WHERE group_id = %s AND user_id = %s",
                    (group_id, user.id),
                )
                if not group_exists or not in_group:
                    await ctx.reply(
                        "You must be a member of the group to buy tickets for it."
                    )
                    return

            user_ticket_count = self.db.fetch_one(
                "SELECT COUNT(*) as count FROM lottery_tickets WHERE USERID = %s",
                (user.id,),
            )["count"]
            if user_ticket_count + amount > MAX_TICKETS_PER_USER:
                await ctx.reply(
                    f"You can only have {MAX_TICKETS_PER_USER} tickets per draw. You currently have {user_ticket_count}."
                )
                return

            # Deduct ticket price
            await update_money(user, wallet=-price)
            self.add_to_jackpot(price)

            # Store tickets in DB (one row per ticket for higher odds with multiple tickets)
            for _ in range(amount):
                self.db.execute_query(
                    "INSERT INTO lottery_tickets (USERID, TIMESTAMP, TICKET_TYPE, group_id) VALUES (%s, %s, %s, %s)",
                    (user.id, datetime.utcnow(), ticket_type, group_id),
                )

            jackpot = self.get_jackpot()
            await ctx.reply(
                f"🎟️ {amount} {ticket_type.capitalize()} ticket(s) bought! Jackpot is now {jackpot:,} coins.",
                mention_author=False,
            )
        except Exception as e:
            logging.error(f"Error in buy_ticket: {e}")
            await ctx.reply(
                "An error occurred while buying your ticket(s).", mention_author=False
            )

    @commands.command(name="lotterystats", help="Show current lottery stats.")
    async def lottery_stats(self, ctx: commands.Context):
        try:
            # Show last winner and their prize
            last_result = self.db.fetch_one(
                "SELECT * FROM lottery_results ORDER BY DRAW_TIME DESC LIMIT 1"
            )
            winner_text = "No draws yet."
            if last_result:
                winner = self.client.get_user(
                    last_result["WINNER_ID"]
                ) or await self.client.fetch_user(last_result["WINNER_ID"])
                winner_text = f"Last winner: {winner.mention if winner else 'Unknown'} ({last_result['AMOUNT']:,} coins)"

            ticket_count = self.db.fetch_one(
                "SELECT COUNT(*) as count FROM lottery_tickets"
            )["count"]
            jackpot = self.get_jackpot()

            # Check if the user has an unclaimed win
            unclaimed = self.db.fetch_one(
                "SELECT * FROM lottery_results WHERE WINNER_ID = %s AND CLAIMED = 0 ORDER BY DRAW_TIME DESC LIMIT 1",
                (ctx.author.id,),
            )
            winner_note = ""
            if unclaimed:
                # Mark as claimed and pay out
                self.db.execute_query(
                    "UPDATE lottery_results SET CLAIMED = 1 WHERE ID = %s",
                    (unclaimed["ID"],),
                )
                await update_money(ctx.author, wallet=unclaimed["AMOUNT"])
                winner_note = f"\n🎉 **You have won {unclaimed['AMOUNT']:,} coins! Your prize has been paid out.**"

            await ctx.reply(
                f"{winner_text}\n"
                f"🎰 There are currently **{ticket_count}** tickets in the pool.\n"
                f"💰 Jackpot: **{jackpot:,}** coins.\n"
                f"Next draw in: {self.time_until_draw()}"
                f"{winner_note}",
                mention_author=False,
            )
        except Exception as e:
            logging.error(f"Error in lottery_stats: {e}")
            await ctx.reply(
                "An error occurred while fetching lottery stats.", mention_author=False
            )

    def time_until_draw(self) -> str:
        now = datetime.utcnow()
        last_draw = self.get_last_draw_time()
        next_draw = last_draw + timedelta(hours=LOTTERY_INTERVAL_HOURS)
        remaining = next_draw - now
        if remaining.total_seconds() < 0:
            return "Drawing soon!"
        hours, remainder = divmod(int(remaining.total_seconds()), 3600)
        minutes, seconds = divmod(remainder, 60)
        return f"{hours}h {minutes}m {seconds}s"

    @tasks.loop(minutes=1)
    async def lottery_draw(self):
        now = datetime.utcnow()
        last_draw = self.get_last_draw_time()
        if (now - last_draw).total_seconds() < LOTTERY_INTERVAL_HOURS * 3600:
            return  # Not time yet

        await self._run_lottery_draw()

    @commands.command(name="testdraw", help="Manually trigger the lottery draw.")
    @commands.is_owner()
    async def testdraw(self, ctx: commands.Context):
        await ctx.send("Starting lottery draw...")
        await self._run_lottery_draw()

    async def _run_lottery_draw(self):
        try:
            await self.client.wait_until_ready()
            tickets = self.db.fetch_all(
                "SELECT USERID, TICKET_TYPE FROM lottery_tickets"
            )
            jackpot = self.get_jackpot()
            if not tickets or jackpot <= 0:
                self.set_jackpot(self.get_jackpot() + ROLLOVER_BONUS)
                self.set_last_draw_time(datetime.utcnow())
                return

            # Build weighted ticket list
            weighted_tickets = []
            for t in tickets:
                luck_row = self.db.fetch_one(
                    "SELECT LUCK FROM lottery_luck WHERE USERID = %s", (t["USERID"],)
                )
                luck = luck_row["LUCK"] if luck_row else 0
                ticket_type_weight = TICKET_TYPES[t["TICKET_TYPE"]]["weight"]
                weight = ticket_type_weight * (1 + luck)
                weighted_tickets.extend([t["USERID"]] * weight)

            winner_id = choice(weighted_tickets)
            # Find group for winning ticket BEFORE deleting tickets
            group_id = self.db.fetch_one(
                "SELECT group_id FROM lottery_tickets WHERE USERID = %s LIMIT 1",
                (winner_id,),
            )["group_id"]

            self.db.execute_query("DELETE FROM lottery_tickets")  # Reset for next round

            if group_id:
                members = self.db.fetch_all(
                    "SELECT user_id FROM lottery_group_members WHERE group_id = %s",
                    (group_id,),
                )
                member_ids = [m["user_id"] for m in members]
                split_prize = (jackpot // 2) // len(member_ids)
                for uid in member_ids:
                    member = self.client.get_user(uid) or await self.client.fetch_user(
                        uid
                    )
                    await update_money(member, wallet=split_prize)
                # Store group win in results with WIN_TYPE='group'
                self.db.execute_query(
                    "INSERT INTO lottery_results (WINNER_ID, AMOUNT, DRAW_TIME, CLAIMED, WIN_TYPE) VALUES (%s, %s, %s, 0, %s)",
                    (group_id, jackpot // 2, datetime.utcnow(), "group"),
                )
            else:
                # Store solo win in results with WIN_TYPE='user'
                self.db.execute_query(
                    "INSERT INTO lottery_results (WINNER_ID, AMOUNT, DRAW_TIME, CLAIMED, WIN_TYPE) VALUES (%s, %s, %s, 0, %s)",
                    (winner_id, jackpot // 2, datetime.utcnow(), "user"),
                )
            # Carry over half the jackpot (rounded down)
            carryover = jackpot // 2
            self.set_jackpot(carryover)
            self.set_last_draw_time(datetime.utcnow())

            # Reset winner's luck, increment others'
            self.db.execute_query(
                "UPDATE lottery_luck SET LUCK = 0 WHERE USERID = %s", (winner_id,)
            )
            self.db.execute_query(
                "UPDATE lottery_luck SET LUCK = LUCK + 1 WHERE USERID != %s",
                (winner_id,),
            )
        except Exception as e:
            logging.error(f"Error in _run_lottery_draw: {e}")

    @lottery_draw.before_loop
    async def before_lottery_draw(self):
        await self.client.wait_until_ready()

    @commands.command(name="lotteryhistory")
    async def lottery_history(self, ctx):
        results = self.db.fetch_all(
            "SELECT * FROM lottery_results ORDER BY DRAW_TIME DESC LIMIT 10"
        )
        if not results:
            await ctx.reply("No lottery draws yet.")
            return
        lines = []
        for res in results:
            if res.get("WIN_TYPE") == "group":
                lines.append(
                    f"{res['DRAW_TIME'].strftime('%Y-%m-%d')}: Group `{res['WINNER_ID']}` won {res['AMOUNT']:,} coins"
                )
            else:
                winner = self.client.get_user(
                    res["WINNER_ID"]
                ) or await self.client.fetch_user(res["WINNER_ID"])
                lines.append(
                    f"{res['DRAW_TIME'].strftime('%Y-%m-%d')}: {winner.mention if winner else 'Unknown'} won {res['AMOUNT']:,} coins"
                )
        await ctx.reply("\n".join(lines))

    @commands.command(name="lotteryleaderboard")
    async def lottery_leaderboard(self, ctx):
        winners = self.db.fetch_all(
            "SELECT WINNER_ID, COUNT(*) as wins, SUM(AMOUNT) as total FROM lottery_results GROUP BY WINNER_ID ORDER BY wins DESC LIMIT 10"
        )
        if not winners:
            await ctx.reply("No winners yet.")
            return
        lines = []
        for w in winners:
            user = self.client.get_user(w["WINNER_ID"]) or await self.client.fetch_user(
                w["WINNER_ID"]
            )
            lines.append(
                f"{user.mention if user else 'Unknown'}: {w['wins']} wins, {w['total']:,} coins"
            )
        await ctx.reply("\n".join(lines))

    @commands.command(name="initluck")
    @commands.is_owner()
    async def init_luck(self, ctx):
        """Initialize or reset the luck of a user."""
        user = ctx.author
        self.db.execute_query(
            "INSERT IGNORE INTO lottery_luck (USERID, LUCK) VALUES (%s, 0)", (user.id,)
        )
        await ctx.reply(f"Your luck has been initialized/reset.")

    @commands.command(name="creategroup")
    async def create_group(self, ctx, group_id: str):
        exists = self.db.fetch_one(
            "SELECT 1 FROM lottery_groups WHERE group_id = %s", (group_id,)
        )
        if exists:
            await ctx.reply("A group with that ID already exists.")
            return
        self.db.execute_query(
            "INSERT INTO lottery_groups (group_id, creator_id) VALUES (%s, %s)",
            (group_id, ctx.author.id),
        )
        self.db.execute_query(
            "INSERT INTO lottery_group_members (group_id, user_id) VALUES (%s, %s)",
            (group_id, ctx.author.id),
        )
        await ctx.reply(f"Group `{group_id}` created and you have joined it.")

    @commands.command(name="joingroup")
    async def join_group(self, ctx, group_id: str):
        exists = self.db.fetch_one(
            "SELECT 1 FROM lottery_groups WHERE group_id = %s", (group_id,)
        )
        if not exists:
            await ctx.reply("That group does not exist.")
            return
        already = self.db.fetch_one(
            "SELECT 1 FROM lottery_group_members WHERE group_id = %s AND user_id = %s",
            (group_id, ctx.author.id),
        )
        if already:
            await ctx.reply("You are already in this group.")
            return
        self.db.execute_query(
            "INSERT INTO lottery_group_members (group_id, user_id) VALUES (%s, %s)",
            (group_id, ctx.author.id),
        )
        await ctx.reply(f"You have joined group `{group_id}`.")

    @commands.command(name="leavegroup")
    async def leave_group(self, ctx, group_id: str):
        self.db.execute_query(
            "DELETE FROM lottery_group_members WHERE group_id = %s AND user_id = %s",
            (group_id, ctx.author.id),
        )
        await ctx.reply(f"You have left group `{group_id}`.")

    @commands.command(name="deletegroup")
    async def delete_group(self, ctx, group_id: str):
        # Check if group exists and if user is creator
        group = self.db.fetch_one(
            "SELECT creator_id FROM lottery_groups WHERE group_id = %s", (group_id,)
        )
        if not group:
            await ctx.reply("That group does not exist.")
            return
        if group["creator_id"] != ctx.author.id:
            await ctx.reply("Only the group creator can delete this group.")
            return
        # Check if group is empty (no members except possibly the creator)
        members = self.db.fetch_all(
            "SELECT user_id FROM lottery_group_members WHERE group_id = %s", (group_id,)
        )
        if len(members) > 1 or (
            len(members) == 1 and members[0]["user_id"] != ctx.author.id
        ):
            await ctx.reply(
                "You can only delete a group if it is empty (no members except you)."
            )
            return
        # Delete group and membership
        self.db.execute_query(
            "DELETE FROM lottery_group_members WHERE group_id = %s", (group_id,)
        )
        self.db.execute_query(
            "DELETE FROM lottery_groups WHERE group_id = %s", (group_id,)
        )
        await ctx.reply(f"Group `{group_id}` has been deleted.")

    @commands.command(name="refundtickets")
    async def refund_tickets(self, ctx, amount: int = 1):
        user = ctx.author
        tickets = self.db.fetch_all(
            "SELECT * FROM lottery_tickets WHERE USERID = %s", (user.id,)
        )
        if not tickets:
            await ctx.reply("You have no tickets to refund for this draw.")
            return

        if amount is None:
            amount = len(tickets)
        if amount < 1 or amount > len(tickets):
            await ctx.reply(
                f"You can only refund between 1 and {len(tickets)} tickets."
            )
            return

        # Refund only the specified amount
        tickets_to_refund = tickets[:amount]
        total_refund = 0
        ticket_ids = []
        for ticket in tickets_to_refund:
            price = TICKET_TYPES[ticket["TICKET_TYPE"]]["price"]
            refund = int(price * 0.8)
            total_refund += refund
            ticket_ids.append(ticket["ID"])

        # Remove only the refunded tickets
        format_strings = ",".join(["%s"] * len(ticket_ids))
        self.db.execute_query(
            f"DELETE FROM lottery_tickets WHERE ID IN ({format_strings})",
            tuple(ticket_ids),
        )
        await update_money(user, wallet=total_refund)
        await ctx.reply(
            f"{amount} ticket(s) refunded for {total_refund:,} coins (80% of purchase price)."
        )


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