# bot.py

import os
import asyncio
from datetime import datetime, timedelta

import nextcord
from nextcord.ext import commands, tasks
from nextcord import Interaction, SlashOption, ChannelType, Permissions
from nextcord.ui import Modal, TextInput, Button, View, Select
from nextcord.utils import get
from dotenv import load_dotenv
import motor.motor_asyncio
import aiohttp

# Load environment variables
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
MONGO_URI = os.getenv('MONGO_URI')
ROLIMONS_API_KEY = os.getenv('ROLIMONS_API_KEY')

# MongoDB setup
mongo_client = motor.motor_asyncio.AsyncIOMotorClient(MONGO_URI)
db = mongo_client['discord_bot']

# Discord Intents
intents = nextcord.Intents.default()
intents.members = True  # Subscribe to the privileged members intent.
intents.message_content = True  # Message content intent required for reading message content.

# Bot setup
bot = commands.Bot(command_prefix='!', intents=intents)

# Replace with your actual IDs
YOUR_GUILD_ID = 123456789012345678  # Replace with your server's ID
YOUR_STAFF_CHANNEL_ID = 123456789012345678  # Replace with your staff channel ID
BLACKLIST_ROLE_ID = 123456789012345678  # Replace with your blacklist role ID
ROLE_ID_FOR_1M = 123456789012345678  # Replace with your role ID for 1M+
ROLE_ID_FOR_100K = 123456789012345678  # Replace with your role ID for 100K+
ROLE_ID_FOR_25K = 123456789012345678  # Replace with your role ID for 25K+

# -------------------------------------------------
# Utility Functions
# -------------------------------------------------



async def get_total_value(roblox_id):
    total_value = 0
    async with aiohttp.ClientSession() as session:
        async with session.get(f'https://www.rolimons.com/api/playerassets/{roblox_id}') as resp:
            if resp.status != 200:
                return None
            data = await resp.json()
            # Implement logic to calculate total_value from data
            # This is a placeholder implementation
            asset_values = data.get('asset_values', {})
            total_value = sum(asset_values.values())
    return total_value

# -------------------------------------------------
# Event Handlers
# -------------------------------------------------

@bot.event
async def on_ready():
    print(f'Logged in as {bot.user} (ID: {bot.user.id})')
    print('------')
    check_blacklists.start()  # Start the background task
    check_purges.start()
    backup_database.start()
    print("Background tasks started.")

@bot.event
async def on_member_update(before, after):
    if before.roles != after.roles:
        await track_roles(after)

@bot.event
async def on_member_join(member):
    await restore_member_roles(member)

# -------------------------------------------------
# Commands and Features
# -------------------------------------------------

# Server Snapshot and Restoration
@bot.slash_command(name='snapshot', description='Take a snapshot of the server structure and configuration.')
@commands.has_permissions(administrator=True)
async def snapshot(interaction: Interaction):
    await interaction.response.defer(ephemeral=True)
    guild = interaction.guild

    # Capture roles
    roles = []
    for role in guild.roles:
        roles.append({
            'id': role.id,
            'name': role.name,
            'permissions': role.permissions.value,
            'color': role.color.value,
            'hoist': role.hoist,
            'position': role.position,
            'mentionable': role.mentionable,
            'managed': role.managed
        })

    # Capture channels
    channels = []
    for channel in guild.channels:
        overwrites = []
        for target, overwrite in channel.overwrites.items():
            target_id = target.id if isinstance(target, (nextcord.Role, nextcord.Member)) else target
            overwrites.append({
                'id': target_id,
                'allow': overwrite.pair()[0].value,
                'deny': overwrite.pair()[1].value
            })

        channels.append({
            'id': channel.id,
            'name': channel.name,
            'type': str(channel.type),
            'position': channel.position,
            'category_id': channel.category_id,
            'overwrites': overwrites
        })

    # Capture messages from important channels (e.g., announcements)
    messages = []
    important_channels = ['announcements', 'rules']  # Add your important channel names
    for channel_name in important_channels:
        channel = get(guild.channels, name=channel_name)
        if channel:
            async for message in channel.history(limit=100):  # Adjust limit as needed
                messages.append({
                    'channel_id': channel.id,
                    'content': message.content,
                    'embeds': [embed.to_dict() for embed in message.embeds],
                    'attachments': [attachment.url for attachment in message.attachments],
                    'author_id': message.author.id,
                    'timestamp': message.created_at.isoformat()
                })

    # Save snapshot to database
    snapshot_data = {
        'guild_id': guild.id,
        'timestamp': datetime.utcnow(),
        'roles': roles,
        'channels': channels,
        'messages': messages
        # Add more data if needed
    }
    await db.snapshots.insert_one(snapshot_data)
    await interaction.followup.send('Server snapshot has been taken.', ephemeral=True)

@bot.slash_command(name='restore', description='Restore the server from the latest snapshot.')
@commands.has_permissions(administrator=True)
async def restore(interaction: Interaction):
    await interaction.response.defer(ephemeral=True)
    guild = interaction.guild

    # Fetch the latest snapshot
    snapshot = await db.snapshots.find_one({'guild_id': guild.id}, sort=[('timestamp', -1)])
    if not snapshot:
        await interaction.followup.send('No snapshot available to restore.', ephemeral=True)
        return

    # Restore roles
    existing_roles = {role.name: role for role in guild.roles}
    role_mapping = {}  # Map old role IDs to new role IDs

    for role_data in snapshot['roles']:
        if role_data['name'] in existing_roles:
            role = existing_roles[role_data['name']]
            await role.edit(
                permissions=nextcord.Permissions(role_data['permissions']),
                color=nextcord.Color(role_data['color']),
                hoist=role_data['hoist'],
                mentionable=role_data['mentionable']
            )
        else:
            role = await guild.create_role(
                name=role_data['name'],
                permissions=nextcord.Permissions(role_data['permissions']),
                color=nextcord.Color(role_data['color']),
                hoist=role_data['hoist'],
                mentionable=role_data['mentionable']
            )
        role_mapping[role_data['id']] = role.id

    # Restore channels
    existing_channels = {channel.name: channel for channel in guild.channels}
    channel_mapping = {}  # Map old channel IDs to new channel IDs

    for channel_data in snapshot['channels']:
        if channel_data['name'] in existing_channels:
            channel = existing_channels[channel_data['name']]
            await channel.edit(
                position=channel_data['position'],
                overwrites={
                    guild.get_role(overwrite['id']) or guild.get_member(overwrite['id']): nextcord.PermissionOverwrite.from_pair(
                        nextcord.Permissions(overwrite['allow']),
                        nextcord.Permissions(overwrite['deny'])
                    ) for overwrite in channel_data['overwrites']
                },
                category=guild.get_channel(channel_data['category_id'])
            )
        else:
            overwrites = {}
            for overwrite_data in channel_data['overwrites']:
                target = guild.get_role(overwrite_data['id']) or guild.get_member(overwrite_data['id'])
                if target:
                    overwrite = nextcord.PermissionOverwrite.from_pair(
                        nextcord.Permissions(overwrite_data['allow']),
                        nextcord.Permissions(overwrite_data['deny'])
                    )
                    overwrites[target] = overwrite

            if channel_data['type'] == 'ChannelType.category':
                channel = await guild.create_category(
                    name=channel_data['name'],
                    overwrites=overwrites,
                    position=channel_data['position']
                )
            elif channel_data['type'] == 'ChannelType.voice':
                channel = await guild.create_voice_channel(
                    name=channel_data['name'],
                    overwrites=overwrites,
                    position=channel_data['position'],
                    category=guild.get_channel(channel_mapping.get(channel_data['category_id']))
                )
            else:
                channel = await guild.create_text_channel(
                    name=channel_data['name'],
                    overwrites=overwrites,
                    position=channel_data['position'],
                    category=guild.get_channel(channel_mapping.get(channel_data['category_id']))
                )
        channel_mapping[channel_data['id']] = channel.id

    # Repost important messages
    for message_data in snapshot.get('messages', []):
        channel = guild.get_channel(channel_mapping.get(message_data['channel_id']))
        if channel:
            await channel.send(
                content=message_data['content'],
                embeds=[nextcord.Embed.from_dict(embed) for embed in message_data['embeds']],
                files=[await download_attachment(url) for url in message_data['attachments']]
            )

    await interaction.followup.send('Server restoration completed.', ephemeral=True)

async def download_attachment(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            if resp.status == 200:
                data = await resp.read()
                return nextcord.File(io.BytesIO(data), filename=url.split('/')[-1])
    return None

# Enhanced Role Management
async def track_roles(member):
    role_ids = [role.id for role in member.roles if role != member.guild.default_role]
    role_data = {
        'user_id': member.id,
        'guild_id': member.guild.id,
        'roles': role_ids,
        'timestamp': datetime.utcnow()
    }
    await db.user_roles.update_one(
        {'user_id': member.id, 'guild_id': member.guild.id},
        {'$set': role_data},
        upsert=True
    )

async def restore_member_roles(member):
    data = await db.user_roles.find_one({'user_id': member.id, 'guild_id': member.guild.id})
    if data:
        role_ids = data.get('roles', [])
        roles = [member.guild.get_role(role_id) for role_id in role_ids if member.guild.get_role(role_id)]
        await member.add_roles(*roles)
        print(f"Restored roles for {member}")

# User Verification and Account Management
@bot.slash_command(name='verify', description='Verify your Roblox account.')
async def verify(interaction: Interaction):
    await interaction.response.defer(ephemeral=True)

    async def message_check(msg):
        return msg.author == interaction.user and isinstance(msg.channel, nextcord.DMChannel)

    try:
        await interaction.user.send('Please enter your Roblox username:')
        username_msg = await bot.wait_for('message', check=message_check, timeout=60)
        username = username_msg.content.strip()

        # Fetch Roblox User ID
        async with aiohttp.ClientSession() as session:
            async with session.get(f'https://api.roblox.com/users/get-by-username?username={username}') as resp:
                data = await resp.json()
                if 'Id' not in data:
                    await interaction.user.send('Invalid Roblox username.')
                    return
                roblox_id = data['Id']

            # Bio phrase verification (e.g., set a code in their bio)
            code_phrase = f"Verification Code: {interaction.user.id}"
            # Instruct user to set code_phrase in their Roblox bio and then confirm
            await interaction.user.send(f"Please set the following phrase in your Roblox bio or status and reply 'done' when finished:\n\n{code_phrase}")
            response_msg = await bot.wait_for('message', check=message_check, timeout=300)
            if response_msg.content.lower() != 'done':
                await interaction.user.send('Verification cancelled.')
                return

            # Check if the code_phrase is present in their bio/status
            # Note: This requires access to Roblox APIs that may require authentication
            # Placeholder implementation:
            # async with session.get(f'https://roblox_api_to_get_user_bio/{roblox_id}') as bio_resp:
            #     bio_data = await bio_resp.json()
            #     if code_phrase not in bio_data.get('bio', ''):
            #         await interaction.user.send('Verification phrase not found in your bio.')
            #         return

            # Alt account detection
            existing_verification = await db.verifications.find_one({'roblox_id': roblox_id})
            if existing_verification and existing_verification['discord_id'] != interaction.user.id:
                # Notify staff
                staff_channel = bot.get_channel(YOUR_STAFF_CHANNEL_ID)
                if staff_channel:
                    embed = nextcord.Embed(title='Alt Account Detected', color=nextcord.Color.red())
                    embed.add_field(name='Discord User', value=interaction.user.mention)
                    embed.add_field(name='Roblox Username', value=username)
                    embed.add_field(name='Roblox ID', value=roblox_id)
                    await staff_channel.send(embed=embed)
                await interaction.user.send('Your verification requires manual approval due to potential alt account detection.')
                return

            # Verify limited values using Rolimons API
            total_value = await get_total_value(roblox_id)
            if total_value is None:
                await interaction.user.send('Failed to retrieve your limited values.')
                return

            # Assign roles based on total_value
            guild = bot.get_guild(YOUR_GUILD_ID)
            member = guild.get_member(interaction.user.id)

            assigned_role = None
            if total_value >= 1_000_000:
                assigned_role = guild.get_role(ROLE_ID_FOR_1M)
            elif total_value >= 100_000:
                assigned_role = guild.get_role(ROLE_ID_FOR_100K)
            elif total_value >= 25_000:
                assigned_role = guild.get_role(ROLE_ID_FOR_25K)

            if assigned_role:
                await member.add_roles(assigned_role)
                await interaction.user.send(f'Verification successful! You have been assigned the {assigned_role.name} role.')
                await db.verifications.update_one(
                    {'discord_id': interaction.user.id},
                    {'$set': {'roblox_id': roblox_id, 'total_value': total_value}},
                    upsert=True
                )
            else:
                await interaction.user.send('You do not meet the value requirements for any role.')

    except asyncio.TimeoutError:
        await interaction.user.send('Verification timed out.')
    except Exception as e:
        print(f'Error during verification: {e}')
        await interaction.user.send('An error occurred during verification.')

# Advanced Moderation Tools
@bot.slash_command(name='report', description='Report a scammer or rule violator.')
async def report(interaction: Interaction):
    modal = Modal(title='Scam Report')

    user_id_input = TextInput(label='User ID', placeholder='Enter the User ID', required=True)
    description_input = TextInput(label='Description', style=nextcord.TextInputStyle.paragraph, required=True)
    proof_input = TextInput(label='Proof (URL)', placeholder='Provide proof URL', required=True)

    modal.add_item(user_id_input)
    modal.add_item(description_input)
    modal.add_item(proof_input)

    async def modal_callback(modal_interaction):
        user_id = user_id_input.value
        description = description_input.value
        proof = proof_input.value

        embed = nextcord.Embed(title='New Scam Report', color=nextcord.Color.orange())
        embed.add_field(name='User ID', value=user_id)
        embed.add_field(name='Description', value=description)
        embed.add_field(name='Proof', value=proof)
        embed.set_footer(text=f'Reported by {interaction.user}', icon_url=interaction.user.avatar.url)
        embed.timestamp = datetime.utcnow()

        staff_channel = bot.get_channel(YOUR_STAFF_CHANNEL_ID)
        if staff_channel:
            # Create approval buttons
            approve_button = Button(label='Approve', style=nextcord.ButtonStyle.green)
            deny_button = Button(label='Deny', style=nextcord.ButtonStyle.red)

            async def approve_callback(button_interaction):
                # Implement role assignment or other actions
                # For example, assign a "Scammer" role to the reported user
                reported_user = bot.get_user(int(user_id))
                if reported_user:
                    member = interaction.guild.get_member(reported_user.id)
                    if member:
                        scammer_role = interaction.guild.get_role(SCAMMER_ROLE_ID)  # Replace with your role ID
                        if scammer_role:
                            await member.add_roles(scammer_role)
                await button_interaction.response.send_message('Report approved and actions taken.', ephemeral=True)

            async def deny_callback(button_interaction):
                await button_interaction.response.send_message('Report denied.', ephemeral=True)

            approve_button.callback = approve_callback
            deny_button.callback = deny_callback

            view = View()
            view.add_item(approve_button)
            view.add_item(deny_button)

            await staff_channel.send(embed=embed, view=view)
            await modal_interaction.response.send_message('Your report has been submitted.', ephemeral=True)
        else:
            await modal_interaction.response.send_message('Failed to submit report. Please contact staff.', ephemeral=True)

    modal.callback = modal_callback
    await interaction.response.send_modal(modal)

@bot.slash_command(name='blacklist', description='Temporarily blacklist a user.')
@commands.has_permissions(manage_roles=True)
async def blacklist(
    interaction: Interaction,
    member: nextcord.Member,
    duration: int = SlashOption(description='Duration in days', min_value=1),
    reason: str = SlashOption(description='Reason for blacklisting', required=False)
):
    blacklist_role = interaction.guild.get_role(BLACKLIST_ROLE_ID)
    if not blacklist_role:
        await interaction.response.send_message('Blacklist role not found.', ephemeral=True)
        return

    await member.add_roles(blacklist_role)
    unblacklist_time = datetime.utcnow() + timedelta(days=duration)
    await db.blacklists.insert_one({
        'user_id': member.id,
        'guild_id': interaction.guild.id,
        'unblacklist_time': unblacklist_time,
        'reason': reason
    })
    await interaction.response.send_message(f'{member.mention} has been blacklisted for {duration} day(s). Reason: {reason}', ephemeral=True)

@tasks.loop(minutes=1)
async def check_blacklists():
    now = datetime.utcnow()
    blacklists = await db.blacklists.find({'unblacklist_time': {'$lte': now}}).to_list(length=100)
    for entry in blacklists:
        guild = bot.get_guild(entry['guild_id'])
        member = guild.get_member(entry['user_id'])
        if member:
            blacklist_role = guild.get_role(BLACKLIST_ROLE_ID)
            if blacklist_role and blacklist_role in member.roles:
                await member.remove_roles(blacklist_role)
        await db.blacklists.delete_one({'_id': entry['_id']})
        print(f"Unblacklisted user {member}")

# Channel Management and Auto-Moderation
@bot.slash_command(name='schedule_purge', description='Schedule a channel to be purged.')
@commands.has_permissions(manage_channels=True)
async def schedule_purge(interaction: Interaction, channel: nextcord.TextChannel, delay: int = SlashOption(description='Delay in minutes', min_value=1)):
    purge_time = datetime.utcnow() + timedelta(minutes=delay)
    await db.channel_purges.insert_one({
        'channel_id': channel.id,
        'purge_time': purge_time
    })
    await interaction.response.send_message(f'Channel {channel.mention} will be purged in {delay} minute(s).', ephemeral=True)

@tasks.loop(minutes=1)
async def check_purges():
    now = datetime.utcnow()
    purges = await db.channel_purges.find({'purge_time': {'$lte': now}}).to_list(length=100)
    for entry in purges:
        channel = bot.get_channel(entry['channel_id'])
        if channel:
            await channel.purge(limit=None)
            print(f"Purged channel {channel.name}")
        await db.channel_purges.delete_one({'_id': entry['_id']})

# Reaction Emoji Automation for specific channels
@bot.event
async def on_message(message):
    if message.author.bot:
        return

    # Auto-moderation for role-ping channels
    if message.channel.name == 'role-ping-channel':  # Replace with your channel name
        # Implement your auto-moderation rules
        # Example: Delete messages containing certain words
        disallowed_words = ['spam', 'badword']
        if any(word in message.content.lower() for word in disallowed_words):
            await message.delete()
            await message.channel.send(f"{message.author.mention}, your message was removed due to disallowed content.", delete_after=5)

    # Reaction emoji automation
    if message.channel.name == 'reaction-emoji-channel':  # Replace with your channel name
        await message.add_reaction('👍')
        await message.add_reaction('👎')

    await bot.process_commands(message)

# Application Process
@bot.slash_command(name='apply', description='Apply for a specific role.')
async def apply(interaction: Interaction):
    # Present a dropdown menu
    roles = {
        'Top Stocker': 'top_stocker',
        'Stocker': 'stocker',
        'Small Stocker': 'small_stocker',
        'In-game Stocker': 'in_game_stocker',
        'Robux Stocker': 'robux_stocker',
        'Exchanger': 'exchanger'
    }

    options = [nextcord.SelectOption(label=role, value=value) for role, value in roles.items()]
    select = Select(placeholder='Choose a role to apply for', options=options)

    async def select_callback(select_interaction):
        selected_role = select.values[0]
        await handle_application(select_interaction, selected_role)

    select.callback = select_callback
    view = View()
    view.add_item(select)
    await interaction.response.send_message('Select the role you wish to apply for:', view=view, ephemeral=True)

async def handle_application(interaction, role_type):
    # Create a private thread or channel
    channel = await interaction.guild.create_text_channel(
        name=f'application-{interaction.user.name}',
        overwrites={
            interaction.guild.default_role: nextcord.PermissionOverwrite(read_messages=False),
            interaction.user: nextcord.PermissionOverwrite(read_messages=True)
        }
    )

    await channel.send(f'{interaction.user.mention}, please provide the following information for your application for the **{role_type.replace("_", " ").title()}** role:')
    # Implement role-specific application instructions and verification processes
    # For example, prompt the user with questions or require certain verifications

    # Example of sending role-specific instructions
    if role_type == 'top_stocker':
        await channel.send('As a Top Stocker, you need to have at least 1M+ in limited values. Please provide proof of your inventory and any relevant experience.')
    elif role_type == 'stocker':
        await channel.send('As a Stocker, you need to have at least 100K+ in limited values. Please provide proof of your inventory and any relevant experience.')
    # Add other role-specific instructions

    await interaction.followup.send(f'An application channel has been created: {channel.mention}', ephemeral=True)


# Regular Automated Backups
@tasks.loop(hours=24)
async def backup_database():
    # Implement your database backup logic here
    # For example, dumping the database to a file and storing it securely
    print('Database backup completed.')

# Error Handling
@bot.event
async def on_command_error(interaction: Interaction, error):
    if isinstance(error, commands.MissingPermissions):
        await interaction.response.send_message('You do not have permission to use this command.', ephemeral=True)
    elif isinstance(error, commands.CommandNotFound):
        pass  # Ignore invalid commands
    else:
        await interaction.response.send_message('An error occurred while processing the command.', ephemeral=True)
        print(f'Error: {error}')

# -------------------------------------------------
# Run the Bot
# -------------------------------------------------

bot.run(TOKEN)


Give the code to the stuff that needs features implemented