import os
from datetime import datetime
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError

import discord
from discord import app_commands
from discord.ext import commands, tasks
from dotenv import load_dotenv

import database


# ==================================================
# CONFIGURATION
# ==================================================

load_dotenv()

TOKEN = os.getenv("DISCORD_TOKEN")

if not TOKEN:
    raise RuntimeError(
        "DISCORD_TOKEN is missing from your .env file."
    )


# ==================================================
# BOT SETUP
# ==================================================

intents = discord.Intents.default()

bot = commands.Bot(
    command_prefix="!",
    intents=intents
)


# ==================================================
# HELPERS
# ==================================================

def get_timezone(config):
    """Return the configured timezone, falling back to UTC."""
    try:
        return ZoneInfo(config["timezone"])
    except ZoneInfoNotFoundError:
        return ZoneInfo("UTC")


def get_local_date(config):
    """Return today's date using the guild's configured timezone."""
    timezone = get_timezone(config)

    return datetime.now(timezone).strftime("%Y-%m-%d")


def create_qotd_embed(question):
    """Create the embed used for the daily question."""

    embed = discord.Embed(
        title="🌟 Question of the Day",
        description=question["question"],
        color=discord.Color.blurple()
    )

    embed.add_field(
        name="🏷️ Category",
        value=question["category"],
        inline=True
    )

    embed.set_footer(
        text="Question of the Day • No answer required"
    )

    return embed


# ==================================================
# BOT READY
# ==================================================

@bot.event
async def on_ready():

    print(
        f"Logged in as {bot.user} "
        f"(ID: {bot.user.id})"
    )

    # Sync slash commands with Discord.
    try:
        synced = await bot.tree.sync()

        print(
            f"Synced {len(synced)} slash commands."
        )

    except Exception as error:
        print(
            f"Command sync failed: {error}"
        )

    # Start the daily scheduler.
    if not daily_qotd.is_running():
        daily_qotd.start()


# ==================================================
# DAILY QOTD SCHEDULER
# ==================================================

@tasks.loop(minutes=1)
async def daily_qotd():

    for guild in bot.guilds:

        try:

            database.create_guild_if_missing(
                guild.id
            )

            config = database.get_guild(
                guild.id
            )

            if not config:
                continue

            # No QOTD channel configured.
            if not config["channel_id"]:
                continue

            timezone = get_timezone(config)

            now = datetime.now(timezone)

            configured_hour = config["post_hour"]
            configured_minute = config["post_minute"]

            # Only execute at the configured time.
            if now.hour != configured_hour:
                continue

            if now.minute != configured_minute:
                continue

            date = now.strftime("%Y-%m-%d")

            # --------------------------------------------------
            # RESTART-SAFE PROTECTION
            # --------------------------------------------------
            #
            # If today's question has already been posted,
            # don't post another one.
            #
            # This information is stored in SQLite, so it
            # survives bot restarts.
            # --------------------------------------------------

            if database.was_question_posted(
                guild.id,
                date
            ):
                continue

            channel = guild.get_channel(
                config["channel_id"]
            )

            if not channel:

                print(
                    f"QOTD channel not found for "
                    f"{guild.name}."
                )

                continue

            # Get today's question.
            #
            # If one hasn't been selected yet,
            # get_daily_question() selects one and
            # permanently associates it with today's date.
            question = database.get_daily_question(
                guild.id,
                date
            )

            if not question:

                print(
                    f"No approved questions available "
                    f"for {guild.name}."
                )

                continue

            try:

                message = await channel.send(
                    embed=create_qotd_embed(
                        question
                    )
                )

                # Only mark the question as posted AFTER
                # Discord successfully accepts the message.
                database.mark_question_posted(
                    guild.id,
                    date,
                    message.id
                )

                print(
                    f"Posted QOTD in {guild.name}: "
                    f"{question['question']}"
                )

            except discord.Forbidden:

                print(
                    f"Missing permissions to post QOTD "
                    f"in #{channel.name} "
                    f"({guild.name})."
                )

            except discord.HTTPException as error:

                print(
                    f"Discord API error while posting "
                    f"in {guild.name}: {error}"
                )

            except Exception as error:

                print(
                    f"Unexpected QOTD error in "
                    f"{guild.name}: {error}"
                )

        except Exception as error:

            # One broken guild should not stop the
            # scheduler for every other guild.
            print(
                f"Scheduler error for "
                f"{guild.name}: {error}"
            )


@daily_qotd.before_loop
async def before_daily_qotd():
    await bot.wait_until_ready()


# ==================================================
# /qotd
# ==================================================

@bot.tree.command(
    name="qotd",
    description="Show today's Question of the Day."
)
async def qotd(
    interaction: discord.Interaction
):

    if interaction.guild is None:

        await interaction.response.send_message(
            "❌ This command can only be used inside a server.",
            ephemeral=True
        )

        return

    config = database.get_guild(
        interaction.guild.id
    )

    date = get_local_date(config)

    question = database.get_daily_question(
        interaction.guild.id,
        date
    )

    if not question:

        await interaction.response.send_message(
            "❌ There are no approved questions available.",
            ephemeral=True
        )

        return

    await interaction.response.send_message(
        embed=create_qotd_embed(question)
    )


# ==================================================
# /suggest
# ==================================================

@bot.tree.command(
    name="suggest",
    description="Suggest a new Question of the Day."
)
@app_commands.describe(
    question="The question you'd like to suggest.",
    category="The category for your question."
)
async def suggest(
    interaction: discord.Interaction,
    question: str,
    category: str = "General"
):

    if interaction.guild is None:

        await interaction.response.send_message(
            "❌ This command can only be used inside a server.",
            ephemeral=True
        )

        return

    question = question.strip()
    category = category.strip()

    if len(question) < 5:

        await interaction.response.send_message(
            "❌ Your question is too short.",
            ephemeral=True
        )

        return

    if len(question) > 500:

        await interaction.response.send_message(
            "❌ Questions can be at most 500 characters.",
            ephemeral=True
        )

        return

    if len(category) > 50:

        await interaction.response.send_message(
            "❌ Categories can be at most 50 characters.",
            ephemeral=True
        )

        return

    suggestion_id = database.create_suggestion(
        interaction.guild.id,
        interaction.user.id,
        interaction.user.display_name,
        question,
        category
    )

    await interaction.response.send_message(
        f"💡 Suggestion **#{suggestion_id}** submitted!\n\n"
        f"An administrator will review it.",
        ephemeral=True
    )


# ==================================================
# SUGGESTION APPROVAL VIEW
# ==================================================

class SuggestionView(discord.ui.View):

    def __init__(self, suggestion_id):
        super().__init__(timeout=300)

        self.suggestion_id = suggestion_id

    @discord.ui.button(
        label="Approve",
        style=discord.ButtonStyle.success,
        emoji="✅"
    )
    async def approve_button(
        self,
        interaction: discord.Interaction,
        button: discord.ui.Button
    ):

        if not interaction.user.guild_permissions.manage_guild:

            await interaction.response.send_message(
                "❌ You need the **Manage Server** permission.",
                ephemeral=True
            )

            return

        suggestion = database.approve_suggestion(
            self.suggestion_id
        )

        if not suggestion:

            await interaction.response.send_message(
                "⚠️ This suggestion has already been reviewed.",
                ephemeral=True
            )

            return

        # Disable both buttons.
        for child in self.children:
            child.disabled = True

        button.label = "Approved"

        await interaction.response.edit_message(
            content=(
                f"✅ **Suggestion #{self.suggestion_id} approved.**\n\n"
                f"**Question:** "
                f"{suggestion['question']}\n"
                f"**Category:** "
                f"{suggestion['category']}"
            ),
            view=self
        )

    @discord.ui.button(
        label="Reject",
        style=discord.ButtonStyle.danger,
        emoji="❌"
    )
    async def reject_button(
        self,
        interaction: discord.Interaction,
        button: discord.ui.Button
    ):

        if not interaction.user.guild_permissions.manage_guild:

            await interaction.response.send_message(
                "❌ You need the **Manage Server** permission.",
                ephemeral=True
            )

            return

        suggestion = database.reject_suggestion(
            self.suggestion_id
        )

        if not suggestion:

            await interaction.response.send_message(
                "⚠️ This suggestion has already been reviewed.",
                ephemeral=True
            )

            return

        # Disable both buttons.
        for child in self.children:
            child.disabled = True

        button.label = "Rejected"

        await interaction.response.edit_message(
            content=(
                f"❌ **Suggestion #{self.suggestion_id} rejected.**"
            ),
            view=self
        )


# ==================================================
# /questions
# ==================================================

@bot.tree.command(
    name="questions",
    description="Review pending question suggestions."
)
@app_commands.checks.has_permissions(
    manage_guild=True
)
async def questions(
    interaction: discord.Interaction
):

    if interaction.guild is None:

        await interaction.response.send_message(
            "❌ This command can only be used inside a server.",
            ephemeral=True
        )

        return

    suggestions = database.get_pending_suggestions(
        interaction.guild.id
    )

    if not suggestions:

        await interaction.response.send_message(
            "✅ There are no pending question suggestions.",
            ephemeral=True
        )

        return

    # Discord has practical message limits.
    # Display the first 10 pending suggestions.
    suggestions = suggestions[:10]

    await interaction.response.send_message(
        f"📋 **Pending Question Suggestions**\n\n"
        f"Showing {len(suggestions)} pending suggestion(s)."
    )

    for suggestion in suggestions:

        embed = discord.Embed(
            title=(
                f"💡 Suggestion "
                f"#{suggestion['id']}"
            ),
            description=suggestion["question"],
            color=discord.Color.orange()
        )

        embed.add_field(
            name="Category",
            value=suggestion["category"],
            inline=True
        )

        embed.add_field(
            name="Suggested by",
            value=suggestion["username"],
            inline=True
        )

        await interaction.channel.send(
            embed=embed,
            view=SuggestionView(
                suggestion["id"]
            )
        )


# ==================================================
# /addquestion
# ==================================================

@bot.tree.command(
    name="addquestion",
    description="Add a question directly to the QOTD pool."
)
@app_commands.checks.has_permissions(
    manage_guild=True
)
@app_commands.describe(
    question="The question to add.",
    category="The question category."
)
async def addquestion(
    interaction: discord.Interaction,
    question: str,
    category: str = "General"
):

    if interaction.guild is None:

        await interaction.response.send_message(
            "❌ This command can only be used inside a server.",
            ephemeral=True
        )

        return

    question = question.strip()
    category = category.strip()

    if len(question) < 5:

        await interaction.response.send_message(
            "❌ Question is too short.",
            ephemeral=True
        )

        return

    if len(question) > 500:

        await interaction.response.send_message(
            "❌ Question is too long.",
            ephemeral=True
        )

        return

    if len(category) > 50:

        await interaction.response.send_message(
            "❌ Category is too long.",
            ephemeral=True
        )

        return

    question_id = database.add_question(
        question,
        category,
        interaction.user.id
    )

    await interaction.response.send_message(
        f"✅ Added question **#{question_id}** "
        f"to the QOTD pool."
    )


# ==================================================
# /removequestion
# ==================================================

@bot.tree.command(
    name="removequestion",
    description="Remove a question from the QOTD pool."
)
@app_commands.checks.has_permissions(
    manage_guild=True
)
@app_commands.describe(
    question_id="The ID of the question to remove."
)
async def removequestion(
    interaction: discord.Interaction,
    question_id: int
):

    if interaction.guild is None:

        await interaction.response.send_message(
            "❌ This command can only be used inside a server.",
            ephemeral=True
        )

        return

    removed = database.remove_question(
        question_id
    )

    if not removed:

        await interaction.response.send_message(
            "❌ Question not found.",
            ephemeral=True
        )

        return

    await interaction.response.send_message(
        f"🗑️ Question **#{question_id}** removed "
        f"from the QOTD pool."
    )


# ==================================================
# /setchannel
# ==================================================

@bot.tree.command(
    name="setchannel",
    description="Set the channel for daily questions."
)
@app_commands.checks.has_permissions(
    manage_guild=True
)
@app_commands.describe(
    channel="The channel where QOTDs will be posted."
)
async def setchannel(
    interaction: discord.Interaction,
    channel: discord.TextChannel
):

    if interaction.guild is None:

        await interaction.response.send_message(
            "❌ This command can only be used inside a server.",
            ephemeral=True
        )

        return

    database.update_guild_settings(
        interaction.guild.id,
        channel_id=channel.id
    )

    await interaction.response.send_message(
        f"✅ Daily questions will be posted in "
        f"{channel.mention}."
    )


# ==================================================
# /settime
# ==================================================

@bot.tree.command(
    name="settime",
    description="Set the daily question posting time."
)
@app_commands.checks.has_permissions(
    manage_guild=True
)
@app_commands.describe(
    hour="Hour in 24-hour format.",
    minute="Minute."
)
async def settime(
    interaction: discord.Interaction,
    hour: int,
    minute: int
):

    if interaction.guild is None:

        await interaction.response.send_message(
            "❌ This command can only be used inside a server.",
            ephemeral=True
        )

        return

    if not 0 <= hour <= 23:

        await interaction.response.send_message(
            "❌ Hour must be between 0 and 23.",
            ephemeral=True
        )

        return

    if not 0 <= minute <= 59:

        await interaction.response.send_message(
            "❌ Minute must be between 0 and 59.",
            ephemeral=True
        )

        return

    database.update_guild_settings(
        interaction.guild.id,
        hour=hour,
        minute=minute
    )

    await interaction.response.send_message(
        f"⏰ Daily question time set to "
        f"**{hour:02d}:{minute:02d}**."
    )


# ==================================================
# /settimezone
# ==================================================

@bot.tree.command(
    name="settimezone",
    description="Set the timezone for daily questions."
)
@app_commands.checks.has_permissions(
    manage_guild=True
)
@app_commands.describe(
    timezone="Example: America/New_York"
)
async def settimezone(
    interaction: discord.Interaction,
    timezone: str
):

    if interaction.guild is None:

        await interaction.response.send_message(
            "❌ This command can only be used inside a server.",
            ephemeral=True
        )

        return

    try:
        ZoneInfo(timezone)

    except ZoneInfoNotFoundError:

        await interaction.response.send_message(
            "❌ Invalid timezone.\n\n"
            "Examples:\n"
            "`America/New_York`\n"
            "`America/Chicago`\n"
            "`America/Denver`\n"
            "`America/Los_Angeles`\n"
            "`Europe/London`\n"
            "`Europe/Paris`",
            ephemeral=True
        )

        return

    database.update_guild_settings(
        interaction.guild.id,
        timezone_name=timezone
    )

    await interaction.response.send_message(
        f"🌎 Timezone set to `{timezone}`."
    )


# ==================================================
# /settings
# ==================================================

@bot.tree.command(
    name="settings",
    description="View QOTD bot settings."
)
async def settings(
    interaction: discord.Interaction
):

    if interaction.guild is None:

        await interaction.response.send_message(
            "❌ This command can only be used inside a server.",
            ephemeral=True
        )

        return

    config = database.get_guild(
        interaction.guild.id
    )

    channel = None

    if config["channel_id"]:
        channel = interaction.guild.get_channel(
            config["channel_id"]
        )

    embed = discord.Embed(
        title="⚙️ QOTD Settings",
        color=discord.Color.blurple()
    )

    embed.add_field(
        name="📢 Channel",
        value=(
            channel.mention
            if channel
            else "Not configured"
        ),
        inline=False
    )

    embed.add_field(
        name="⏰ Time",
        value=(
            f"{config['post_hour']:02d}:"
            f"{config['post_minute']:02d}"
        ),
        inline=True
    )

    embed.add_field(
        name="🌎 Timezone",
        value=config["timezone"],
        inline=True
    )

    await interaction.response.send_message(
        embed=embed
    )


# ==================================================
# GLOBAL COMMAND ERROR HANDLING
# ==================================================

@bot.tree.error
async def on_app_command_error(
    interaction: discord.Interaction,
    error: app_commands.AppCommandError
):

    if isinstance(
        error,
        app_commands.errors.MissingPermissions
    ):

        message = (
            "❌ You need the **Manage Server** "
            "permission to use this command."
        )

    else:

        print(
            f"Command error: {error}"
        )

        message = (
            "❌ Something went wrong. "
            "Check the bot log for details."
        )

    try:

        if interaction.response.is_done():

            await interaction.followup.send(
                message,
                ephemeral=True
            )

        else:

            await interaction.response.send_message(
                message,
                ephemeral=True
            )

    except discord.HTTPException:
        pass


# ==================================================
# INITIALIZE DATABASE
# ==================================================

database.init_database()


# ==================================================
# CPANEL / PASSENGER START FUNCTION
# ==================================================

def start_bot():
    """
    Starts the Discord bot.

    Passenger imports this module through
    passenger_wsgi.py, which calls this function.
    """

    bot.run(TOKEN)
