import asyncio
import os
import logging
from typing import Any, Optional, Literal, Union

from disagreement import HybridContext

from disagreement.client import Client
from disagreement.ext.commands.cog import Cog
from disagreement.ext.commands.core import CommandContext
from disagreement.ext.app_commands.decorators import (
    slash_command,
    user_command,
    message_command,
    hybrid_command,
)
from disagreement.ext.app_commands.commands import (
    AppCommandGroup,
)  # For defining groups
from disagreement.ext.app_commands.context import AppCommandContext  # Added
from disagreement.models import (
    User,
    Member,
    Role,
    Attachment,
    Message,
    Channel,
)  # For type hints
from disagreement.enums import (
    ChannelType,
)  # For channel option type hints, assuming it exists

# from disagreement.interactions import Interaction # Replaced by AppCommandContext

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

try:
    from dotenv import load_dotenv
except ImportError:  # pragma: no cover - example helper
    load_dotenv = None
    print("python-dotenv is not installed. Environment variables will not be loaded")

if load_dotenv:
    load_dotenv()


# --- Define a Test Cog ---
class TestCog(Cog):
    def __init__(self, client: Client):
        super().__init__(client)

    @slash_command(name="greet", description="Sends a greeting.")
    async def greet_slash(self, ctx: AppCommandContext, name: str):
        await ctx.send(f"Hello, {name}! (Slash)")

    @user_command(name="Show User Info")
    async def show_user_info_user(
        self, ctx: AppCommandContext, user: User
    ):  # Target user is in ctx.interaction.data.target_id and resolved
        target_user = (
            ctx.interaction.data.resolved.users.get(ctx.interaction.data.target_id)
            if ctx.interaction.data
            and ctx.interaction.data.resolved
            and ctx.interaction.data.target_id
            else user
        )
        if target_user:
            await ctx.send(
                f"User: {target_user.username}#{target_user.discriminator} (ID: {target_user.id}) (User Cmd)",
                ephemeral=True,
            )
        else:
            await ctx.send("Could not find user information.", ephemeral=True)

    @message_command(name="Quote Message")
    async def quote_message_msg(
        self, ctx: AppCommandContext, message: Message
    ):  # Target message is in ctx.interaction.data.target_id and resolved
        target_message = (
            ctx.interaction.data.resolved.messages.get(ctx.interaction.data.target_id)
            if ctx.interaction.data
            and ctx.interaction.data.resolved
            and ctx.interaction.data.target_id
            else message
        )
        if target_message:
            await ctx.send(
                f'Quoting {target_message.author.username}: "{target_message.content}" (Message Cmd)',
                ephemeral=True,
            )
        else:
            await ctx.send("Could not find message to quote.", ephemeral=True)

    @hybrid_command(name="ping", description="Checks bot latency.", aliases=["pong"])
    async def ping_hybrid(
        self, ctx: Union[CommandContext, AppCommandContext], arg: Optional[str] = None
    ):
        latency = self.client.latency
        latency_ms = f"{latency * 1000:.0f}" if latency is not None else "N/A"
        hybrid = HybridContext(ctx)
        await hybrid.send(f"Pong! {latency_ms}ms. Arg: {arg} (Hybrid)")

    @slash_command(name="options_test", description="Tests various option types.")
    async def options_test_slash(
        self,
        ctx: AppCommandContext,
        text: str,
        integer: int,
        boolean: bool,
        number: float,
        user_option: User,
        role_option: Role,
        attachment_option: Attachment,
        choice_option_str: Literal["apple", "banana", "cherry"],
        # Channel and member options as well as numeric Literal choices are
        # not yet exercised in tests pending full library support.
    ):
        response_parts = [
            f"Text: {text}",
            f"Integer: {integer}",
            f"Boolean: {boolean}",
            f"Number: {number}",
            f"User: {user_option.username}#{user_option.discriminator}",
            f"Role: {role_option.name}",
            f"Attachment: {attachment_option.filename} (URL: {attachment_option.url})",
            f"Choice Str: {choice_option_str}",
        ]
        await ctx.send("\n".join(response_parts), ephemeral=True)

    # --- Subcommand Group Test ---
    # Define the group as a class attribute.
    # The AppCommandHandler's discovery mechanism (via Cog) should pick up AppCommandGroup instances.
    settings_group = AppCommandGroup(
        name="settings",
        description="Manage bot settings.",
        # guild_ids can be added here if the group is guild-specific
    )

    @slash_command(
        name="show", description="Shows current setting values.", parent=settings_group
    )
    async def settings_show(
        self, ctx: AppCommandContext, setting_name: Optional[str] = None
    ):
        if setting_name:
            await ctx.send(
                f"Showing value for setting: {setting_name} (Value: Placeholder)",
                ephemeral=True,
            )
        else:
            await ctx.send(
                "Showing all settings: (Placeholder for all settings)", ephemeral=True
            )

    @slash_command(
        name="update", description="Updates a setting.", parent=settings_group
    )
    async def settings_update(
        self, ctx: AppCommandContext, setting_name: str, value: str
    ):
        await ctx.send(
            f"Updated setting: {setting_name} to value: {value}", ephemeral=True
        )

    # The Cog's metaclass or command registration logic should handle adding `settings_group`
    # (and its subcommands) to the client's AppCommandHandler.
    # The decorators now handle associating subcommands with their parent group.

    @slash_command(
        name="numeric_choices_test", description="Tests integer and float choices."
    )
    async def numeric_choices_test_slash(
        self,
        ctx: AppCommandContext,
        int_choice: Literal[10, 20, 30, 42],
        float_choice: float,
    ):
        response = (
            f"Integer Choice: {int_choice} (Type: {type(int_choice).__name__})\n"
            f"Float Choice: {float_choice} (Type: {type(float_choice).__name__})"
        )
        await ctx.send(response, ephemeral=True)

    @slash_command(
        name="numeric_choices_extended",
        description="Tests additional integer and float choice handling.",
    )
    async def numeric_choices_extended_slash(
        self,
        ctx: AppCommandContext,
        int_choice: Literal[-5, 0, 5],
        float_choice: float,
    ):
        response = (
            f"Int Choice: {int_choice} (Type: {type(int_choice).__name__})\n"
            f"Float Choice: {float_choice} (Type: {type(float_choice).__name__})"
        )
        await ctx.send(response, ephemeral=True)

    @slash_command(
        name="channel_member_test",
        description="Tests channel and member options.",
    )
    async def channel_member_test_slash(
        self,
        ctx: AppCommandContext,
        channel: Channel,
        member: Member,
    ):
        response = (
            f"Channel: {channel.name} (Type: {channel.type.name})\n"
            f"Member: {member.username}#{member.discriminator}"
        )
        await ctx.send(response, ephemeral=True)

    @slash_command(
        name="channel_types_test",
        description="Demonstrates multiple channel type options.",
    )
    async def channel_types_test_slash(
        self,
        ctx: AppCommandContext,
        text_channel: Channel,
        voice_channel: Channel,
        category_channel: Channel,
    ):
        response = (
            f"Text: {text_channel.type.name}\n"
            f"Voice: {voice_channel.type.name}\n"
            f"Category: {category_channel.type.name}"
        )
        await ctx.send(response, ephemeral=True)


# --- Main Bot Script ---
async def main():
    bot_token = os.getenv("DISCORD_BOT_TOKEN")
    application_id = os.getenv("DISCORD_APPLICATION_ID")

    if not bot_token:
        logger.error("Error: DISCORD_BOT_TOKEN environment variable not set.")
        return
    if not application_id:
        logger.error("Error: DISCORD_APPLICATION_ID environment variable not set.")
        return

    client = Client(token=bot_token, command_prefix="!", application_id=application_id)

    @client.event
    async def on_ready():
        if client.user:
            logger.info(
                f"Bot logged in as {client.user.username}#{client.user.discriminator}"
            )
        else:
            logger.error(
                "Client ready, but client.user is not populated! This should not happen."
            )
            return  # Avoid proceeding if basic client info isn't there

        if client.application_id:
            logger.info(f"Application ID is: {client.application_id}")
            # Sync application commands (global in this case)
            try:
                logger.info("Attempting to sync application commands...")
                # Ensure application_id is not None before passing
                app_id_to_sync = client.application_id
                if (
                    app_id_to_sync is not None
                ):  # Redundant due to outer if, but good for clarity
                    await client.app_command_handler.sync_commands(
                        application_id=app_id_to_sync
                    )
                    logger.info("Application commands synced successfully.")
                else:  # Should not be reached if outer if client.application_id is true
                    logger.error(
                        "Application ID was None despite initial check. Skipping sync."
                    )
            except Exception as e:
                logger.error(f"Error syncing application commands: {e}", exc_info=True)
        else:
            # This case should be less likely now that Client gets it from READY.
            # If DISCORD_APPLICATION_ID was critical as a fallback, that logic would be here.
            # For now, we rely on the READY event.
            logger.warning(
                "Client's application ID is not set after READY. Skipping application command sync."
            )
            # Check if the environment variable was provided, as a diagnostic.
            if not application_id:
                logger.warning(
                    "DISCORD_APPLICATION_ID environment variable was also not provided."
                )

    client.add_cog(TestCog(client))

    try:
        await client.run()
    except KeyboardInterrupt:
        logger.info("Bot shutting down...")
    except Exception as e:
        logger.error(
            f"An error occurred in the bot's main run loop: {e}", exc_info=True
        )
    finally:
        if not client.is_closed():
            await client.close()
        logger.info("Bot has been closed.")


if __name__ == "__main__":
    # For Windows, to allow graceful shutdown with Ctrl+C
    if os.name == "nt":
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        logger.info("Main loop interrupted. Exiting.")
