import disnake
from disnake.ext import commands
import json
import os
from typing import List, Optional, Dict
import datetime
import asyncio
import logging
from utils.premium import check_premium_tier

kuby_logger = logging.getLogger("KubyBot")

# Chemins ABSOLUS
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(BASE_DIR, "..", "data")
CONV_SETTINGS_FILE = os.path.join(DATA_DIR, "convocation_settings.json")
CONV_BACKUPS_FILE = os.path.join(DATA_DIR, "convocation_backups.json")

# Créer le dossier data/ s'il n'existe pas
os.makedirs(DATA_DIR, exist_ok=True)

def load_json(filepath: str) -> Dict:
    if not os.path.exists(filepath):
        return {}
    try:
        with open(filepath, "r", encoding='utf-8') as f:
            return json.load(f)
    except (json.JSONDecodeError, FileNotFoundError):
        return {}

def save_json(filepath: str, data: Dict):
    os.makedirs(os.path.dirname(filepath), exist_ok=True)
    with open(filepath, "w", encoding='utf-8') as f:
        json.dump(data, f, indent=4, ensure_ascii=False)

class Convocation(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
        self.settings = load_json(CONV_SETTINGS_FILE)
        self.backups = load_json(CONV_BACKUPS_FILE)

    def get_guild_settings(self, guild_id: int) -> Dict:
        return self.settings.get(str(guild_id), {})

    def save_guild_settings(self, guild_id: int, guild_settings: Dict):
        self.settings[str(guild_id)] = guild_settings
        save_json(CONV_SETTINGS_FILE, self.settings)

    def get_backup(self, guild_id: int, user_id: int) -> Optional[Dict]:
        key = f"{guild_id}_{user_id}"
        return self.backups.get(key)

    def save_backup(self, guild_id: int, user_id: int, backup_data: Dict):
        key = f"{guild_id}_{user_id}"
        self.backups[key] = backup_data
        save_json(CONV_BACKUPS_FILE, self.backups)

    def clear_backup(self, guild_id: int, user_id: int):
        key = f"{guild_id}_{user_id}"
        if key in self.backups:
            del self.backups[key]
            save_json(CONV_BACKUPS_FILE, self.backups)

    @commands.slash_command(name="convocation", description="Gère les convocations de membres")
    @check_premium_tier()
    @commands.has_permissions(manage_roles=True)
    async def convocation_group(self, interaction: disnake.ApplicationCommandInteraction):
        pass

    @convocation_group.sub_command(name="convoquer", description="Convoque un membre et verrouille ses accès")
    async def convoquer(self, interaction: disnake.ApplicationCommandInteraction, 
                        membre: disnake.Member = commands.Param(description="Le membre à convoquer"), 
                        raison: str = commands.Param(description="La raison de la convocation"), 
                        heure: str = commands.Param(description="L'heure de la convocation (ex: 14:30)")):
        guild_id = interaction.guild.id
        settings = self.get_guild_settings(guild_id)
        role_conv_id = settings.get("role_id")
        log_channel_id = settings.get("log_channel_id")
        attente_id = settings.get("attente_id") # Salon d'attente à ignorer
        admin_ids = settings.get("admin_ids", [])

        if not role_conv_id:
            await interaction.response.send_message("❌ Le rôle de convocation n'est pas configuré. Utilisez `/convocation config`.", ephemeral=True)
            return

        await interaction.response.defer(ephemeral=True)

        try:
            # --- BACKUP ---
            bot_top_role = interaction.guild.me.top_role
            backup_data = {
                "roles": [r.id for r in membre.roles if not r.is_default() and r.id != role_conv_id and r.position < bot_top_role.position],
                "overrides": {}
            }

            # 1. Backup and Remove current roles
            current_roles = [interaction.guild.get_role(rid) for rid in backup_data["roles"] if interaction.guild.get_role(rid)]
            if current_roles:
                try:
                    await membre.remove_roles(*current_roles, reason=f"Convocation par {interaction.author}")
                except disnake.Forbidden:
                    await interaction.followup.send("⚠️ Je n'ai pas pu retirer certains rôles (Permission refusée).", ephemeral=True)

            # 2. Dynamic Lockdown: Loop through ALL channels
            semaphore = asyncio.Semaphore(10)
            
            async def lock_channel(channel):
                async with semaphore:
                    # On ignore le salon d'attente et ses parents s'il y en a
                    if attente_id and (channel.id == attente_id or (hasattr(channel, 'category') and channel.category and channel.category.id == attente_id)):
                        return False
                    
                    # Check existing override for the member
                    overwrites = channel.overwrites_for(membre)
                    if not overwrites.is_empty():
                        # Save existing override
                        backup_data["overrides"][str(channel.id)] = {k: v for k, v in dict(overwrites).items() if v is not None}
                    
                    # Apply Lockdown override for the member
                    try:
                        await channel.set_permissions(membre, view_channel=False, reason="Convocation Lockdown")
                        return True
                    except disnake.Forbidden:
                        return False
                    except Exception as e:
                        kuby_logger.warning(f"⚠️ [Convocation] Impossible de verrouiller {channel.name}: {e}")
                        return False

            tasks = [lock_channel(c) for c in interaction.guild.channels]
            results = await asyncio.gather(*tasks)
            lockdown_count = sum(1 for r in results if r is True)

            self.save_backup(guild_id, membre.id, backup_data)

            # 3. Add Convocation role
            role_conv = interaction.guild.get_role(role_conv_id)
            if role_conv:
                if role_conv.position >= bot_top_role.position:
                    await interaction.followup.send(f"⚠️ Je ne peux pas donner le rôle {role_conv.mention} car il est plus haut que moi !", ephemeral=True)
                else:
                    try:
                        await membre.add_roles(role_conv, reason="Début de convocation")
                    except disnake.Forbidden:
                        await interaction.followup.send(f"⚠️ Impossible de donner le rôle {role_conv.name} (Permission refusée).", ephemeral=True)

            # 4. Embeds & Logs (Inchangés mais inclus pour la cohérence)
            embed_conv = disnake.Embed(
                title="CONVOCATION | 🔖",
                description=f"> Bonjour {membre.mention}, vous êtes **convoqué** par {interaction.author.mention}. Voici les **détails** :",
                color=0x0099ff,
                timestamp=disnake.utils.utcnow()
            )
            embed_conv.add_field(name="**Raison de la convocation :**", value=f"➜ {raison}", inline=False)
            embed_conv.add_field(name="**Heure de la convocation :**", value=f"➜ {heure}", inline=False)
            embed_conv.add_field(name="\u200B", value=(
                "> Merci de vous présenter dans le vocal attente **5 minutes** avant l'heure.\n\n"
                "> La **levée** de suspension se fera uniquement à la **fin** de la convocation."
            ), inline=False)
            embed_conv.set_footer(text=f"Gestion de {interaction.guild.name}")

            embed_log = disnake.Embed(
                title="📋 CONVOCATION ENVOYÉE",
                color=0xff9900,
                timestamp=disnake.utils.utcnow()
            )
            embed_log.add_field(name="👤 Membre convoqué", value=f"{membre.mention} ({membre.id})", inline=True)
            embed_log.add_field(name="👮 Convoqué par", value=f"{interaction.author.mention}", inline=True)
            embed_log.add_field(name="📄 Raison", value=raison, inline=False)
            embed_log.add_field(name="🕒 Heure", value=heure, inline=False)

            # DM
            dm_sent = False
            try:
                await membre.send(embed=embed_conv)
                dm_sent = True
            except disnake.Forbidden:
                dm_sent = False

            # Admin & Channel Logs
            for admin_id in admin_ids:
                try:
                    admin = await self.bot.fetch_user(admin_id)
                    await admin.send(embed=embed_log)
                except: pass

            if log_channel_id:
                chan = interaction.guild.get_channel(log_channel_id)
                if chan: await chan.send(embed=embed_log)

            await interaction.followup.send(f"✅ Convocation envoyée à {membre.mention}. ({lockdown_count} salons verrouillés) " + 
                           ("" if dm_sent else "(⚠️ DM impossible)"), ephemeral=True)

        except Exception as e:
            import traceback
            traceback.print_exc()
            await interaction.followup.send(f"❌ Une erreur critique est survenue : {e}", ephemeral=True)

    @convocation_group.sub_command(name="lever", description="Lève la convocation d'un membre et restaure ses accès")
    async def lever(self, interaction: disnake.ApplicationCommandInteraction, 
                    membre: disnake.Member = commands.Param(description="Le membre à libérer")):
        guild_id = interaction.guild.id
        settings = self.get_guild_settings(guild_id)
        role_conv_id = settings.get("role_id")
        
        await interaction.response.defer(ephemeral=True)

        backup_data = self.get_backup(guild_id, membre.id)
        if not backup_data:
            await interaction.followup.send("⚠️ Aucune donnée de convocation trouvée pour ce membre.", ephemeral=True)
            return

        # 1. Remove Convocation role & Restore original roles FIRST
        try:
            if role_conv_id:
                role_conv = interaction.guild.get_role(role_conv_id)
                if role_conv and role_conv in membre.roles:
                    await membre.remove_roles(role_conv, reason="Fin de convocation")

            role_ids = backup_data.get("roles", [])
            if role_ids:
                restored_roles = []
                for rid in role_ids:
                    role = interaction.guild.get_role(rid)
                    if role and role.position < interaction.guild.me.top_role.position:
                        restored_roles.append(role)
                
                if restored_roles:
                    await membre.add_roles(*restored_roles, reason="Restauration fin de convocation")
        except Exception as e:
            kuby_logger.error(f"❌ [Convocation] Erreur lors de la restauration des rôles pour {membre}: {e}")

        # 2. Restore channel overrides (Parallel)
        semaphore = asyncio.Semaphore(10)
        overrides = backup_data.get("overrides", {})
        
        async def restore_channel(channel):
            async with semaphore:
                chan_id_str = str(channel.id)
                try:
                    if chan_id_str in overrides:
                        # Restore previous override
                        ov_data = overrides[chan_id_str]
                        overwrite = disnake.PermissionOverwrite(**ov_data)
                        await channel.set_permissions(membre, overwrite=overwrite, reason="Restauration fin de convocation")
                        return "restored"
                    elif membre in channel.overwrites:
                        # Clear our lockdown override only if it exists
                        await channel.set_permissions(membre, overwrite=None, reason="Fin de convocation - Cleanup")
                        return "cleared"
                except disnake.Forbidden:
                    return "forbidden"
                except Exception as e:
                    kuby_logger.warning(f"⚠️ [Convocation] Erreur restauration {channel.name}: {e}")
                    return "error"
                return "skipped"

        tasks = [restore_channel(c) for c in interaction.guild.channels]
        results = await asyncio.gather(*tasks)
        
        restore_count = sum(1 for r in results if r == "restored")
        clear_count = sum(1 for r in results if r == "cleared")
        forbidden_count = sum(1 for r in results if r == "forbidden")
        
        kuby_logger.info(f"🔓 [Convocation] Restauration terminée pour {membre}: {restore_count} restaurés, {clear_count} nettoyés, {forbidden_count} échecs permissions.")
        
        self.clear_backup(guild_id, membre.id)

        # 4. Notification
        embed = disnake.Embed(
            title="🔓 CONVOCATION TERMINÉE",
            description=f"Votre convocation sur **{interaction.guild.name}** est terminée. Vos accès ont été restaurés.",
            color=disnake.Color.green()
        )
        try:
            await membre.send(embed=embed)
        except: pass

        await interaction.followup.send(f"✅ Convocation levée pour {membre.mention}. Access restaurés.", ephemeral=True)

    @convocation_group.sub_command(name="config", description="Configure le système de convocation")
    async def config(self, interaction: disnake.ApplicationCommandInteraction, 
                     role: disnake.Role = commands.Param(description="Le rôle de convocation (Muet/Convocation)"), 
                     channel: disnake.TextChannel = commands.Param(None, description="Le salon de logs"), 
                     attente: disnake.abc.GuildChannel = commands.Param(None, description="Salon (ou catégorie) d'attente à laisser visible"), 
                     admins: str = commands.Param(None, description="Liste d'IDs d'admins à notifier (séparés par des espaces)")):
        guild_id = interaction.guild.id
        settings = self.get_guild_settings(guild_id)
        
        settings["role_id"] = role.id
        if channel:
            settings["log_channel_id"] = channel.id
        if attente:
            settings["attente_id"] = attente.id
        
        if admins:
            admin_list = []
            for uid in admins.split():
                if uid.isdigit():
                    admin_list.append(int(uid))
            settings["admin_ids"] = admin_list

        self.save_guild_settings(guild_id, settings)
        
        embed = disnake.Embed(title="⚙️ Configuration Convocation Mise à jour", color=disnake.Color.blue())
        embed.add_field(name="Rôle", value=role.mention)
        embed.add_field(name="Salon Logs", value=channel.mention if channel else "Non défini")
        embed.add_field(name="Salon/Cat d'attente", value=attente.mention if attente else "Non défini")
        embed.add_field(name="Admins notifiés", value=str(len(settings.get("admin_ids", []))))
        
        await interaction.response.send_message(embed=embed, ephemeral=True)

def setup(bot):
    bot.add_cog(Convocation(bot))
