"""
Cog : website_sync
Commandes pour gérer la synchronisation JSON du site web.

Slash commands disponibles :
  /json-generate [cible]                    — Génère les JSON manuellement
  /json-status                              — État des fichiers + prochaine génération
  /json-preview [cible] [limite]            — Aperçu sans écrire les fichiers
  /json-history [limite]                    — Historique des N dernières générations
  /json-config show                         — Affiche la config actuelle
  /json-config set-guild <id>               — Définit le serveur cible (members.json)
  /json-config set-schedule <h> [m]         — Heure de génération auto
  /json-config set-output-dir <chemin>      — Changer le dossier de sortie
  /json-config add-founder-role <nom>       — Ajouter un nom de rôle fondateur
  /json-config remove-founder-role <nom>    — Retirer un nom de rôle fondateur
  /json-config server-field <champ> <actif> — Active/désactive un champ servers.json
  /json-config member-field <champ> <actif> — Active/désactive un champ members.json
  /json-config reset                        — Remet la config par défaut
"""

import asyncio
import json
import logging
from datetime import datetime, timezone, timedelta
from pathlib import Path
from typing import Optional

import disnake
from disnake.ext import commands, tasks

from src.json_export_service import (
    load_config, save_config, generate_all,
    generate_servers_json, generate_members_json,
    preview_servers, preview_members,
    get_history, log_history,
    SERVERS_FILE, MEMBERS_FILE, OUTPUT_DIR,
    DEFAULT_CONFIG,
)

logger = logging.getLogger("kuby.website_sync")

# Listes des champs disponibles (pour les choix des commandes)
SERVER_FIELDS = [
    "id", "name", "icon", "banner", "owner",
    "memberCount", "description", "createdAt",
    "boostLevel", "boostCount", "verificationLevel",
]
MEMBER_FIELDS = [
    "id", "username", "displayName", "globalName",
    "avatar", "bio", "roles", "joinedAt", "createdAt",
    "badges", "accentColor", "isBot",
]


class WebsiteSync(commands.Cog, name="Website Sync"):
    """Synchronisation JSON pour le site web."""

    def __init__(self, bot: commands.Bot):
        self.bot = bot
        self._scheduler_task: Optional[asyncio.Task] = None
        self.daily_export.start()

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

    # ── Tâche de fond : génération (1 heure) ────────────────────────────
    @tasks.loop(hours=1)
    async def daily_export(self):
        """Génère les JSON toutes les heures."""
        logger.info("[JSON Export] Génération automatique déclenchée.")
        await generate_all(self.bot)

    @daily_export.before_loop
    async def before_daily_export(self):
        await self.bot.wait_until_ready()
        config = load_config()
        logger.info(
            f"[JSON Export] Mode Test actif → génération automatique toutes les 30 secondes."
        )

    # ── Helper : embed de réponse ─────────────────────────────────────────────
    @staticmethod
    def _embed(title: str, color: disnake.Color, description: str = "") -> disnake.Embed:
        e = disnake.Embed(title=title, color=color, timestamp=datetime.now(timezone.utc))
        if description:
            e.description = description
        return e

    # ══════════════════════════════════════════════════════════════════════════
    # /json-generate
    # ══════════════════════════════════════════════════════════════════════════
    @commands.slash_command(name="json-generate", description="Génère manuellement les fichiers JSON du site web")
    @commands.has_permissions(administrator=True)
    async def json_generate(self, interaction: disnake.ApplicationCommandInteraction, 
                            cible: str = commands.Param("all", description="Quoi générer ?", choices={
                                "Tout (servers + members)": "all",
                                "Serveurs uniquement": "servers",
                                "Membres uniquement": "members"
                            })):
        await interaction.response.defer(ephemeral=True)
        start = datetime.now()
        embed = self._embed("⏳ Génération en cours...", disnake.Color.yellow())
        await interaction.followup.send(embed=embed)

        try:
            if cible == "servers":
                res = await generate_servers_json(self.bot)
                embed = self._embed("✅ servers.json généré", disnake.Color.green())
                embed.add_field(name="Serveurs", value=str(res["count"]), inline=True)
                embed.add_field(name="Durée", value=f"{(datetime.now()-start).total_seconds():.1f}s", inline=True)
                embed.add_field(name="Fichier", value=f"`{SERVERS_FILE}`", inline=False)

            elif cible == "members":
                res = await generate_members_json(self.bot)
                embed = self._embed("✅ members.json généré", disnake.Color.green())
                embed.add_field(name="Membres",  value=str(res["count"]),    inline=True)
                embed.add_field(name="Serveur",  value=res["guildName"],     inline=True)
                embed.add_field(name="Durée",    value=f"{(datetime.now()-start).total_seconds():.1f}s", inline=True)
                embed.add_field(name="Fichier",  value=f"`{MEMBERS_FILE}`",  inline=False)

            else:  # all
                res = await generate_all(self.bot, trigger="manual")
                has_errors = bool(res["errors"])
                embed = self._embed(
                    "⚠️ Génération partielle" if has_errors else "✅ Génération complète",
                    disnake.Color.orange() if has_errors else disnake.Color.green(),
                )
                if res["servers"]:
                    embed.add_field(name="📋 Serveurs", value=f"{res['servers']['count']} serveurs", inline=True)
                if res["members"]:
                    embed.add_field(name="👥 Membres",  value=f"{res['members']['count']} membres", inline=True)
                embed.add_field(name="Durée totale", value=f"{(datetime.now()-start).total_seconds():.1f}s", inline=True)
                if has_errors:
                    embed.add_field(name="❌ Erreurs", value="\n".join(res["errors"]), inline=False)
                embed.add_field(name="📁 Dossier", value=f"`{OUTPUT_DIR}`", inline=False)

        except Exception as e:
            embed = self._embed("❌ Erreur", disnake.Color.red(), f"```{e}```")

        await interaction.edit_original_response(embed=embed)

    # ══════════════════════════════════════════════════════════════════════════
    # /json-status
    # ══════════════════════════════════════════════════════════════════════════
    @commands.slash_command(name="json-status", description="État des fichiers JSON et prochaine génération auto")
    @commands.has_permissions(administrator=True)
    async def json_status(self, interaction: disnake.ApplicationCommandInteraction):
        await interaction.response.defer(ephemeral=True)
        config = load_config()
        embed  = self._embed("📊 Statut JSON Export", disnake.Color.blurple())

        # servers.json
        if SERVERS_FILE.exists():
            try:
                data  = json.loads(SERVERS_FILE.read_text())
                size  = SERVERS_FILE.stat().st_size / 1024
                gen_at = datetime.fromisoformat(data["generatedAt"]).strftime("%d/%m/%Y %H:%M")
                embed.add_field(
                    name="🌐 servers.json",
                    value=f"✅ {data['count']} serveurs — {size:.1f} KB\n📅 {gen_at}",
                    inline=False,
                )
            except Exception:
                embed.add_field(name="🌐 servers.json", value="⚠️ Fichier corrompu", inline=False)
        else:
            embed.add_field(name="🌐 servers.json", value="❌ Non généré", inline=False)

        # members.json
        if MEMBERS_FILE.exists():
            try:
                data  = json.loads(MEMBERS_FILE.read_text())
                size  = MEMBERS_FILE.stat().st_size / 1024
                gen_at = datetime.fromisoformat(data["generatedAt"]).strftime("%d/%m/%Y %H:%M")
                embed.add_field(
                    name="👥 members.json",
                    value=f"✅ {data['count']} membres ({data['guildName']}) — {size:.1f} KB\n📅 {gen_at}",
                    inline=False,
                )
            except Exception:
                embed.add_field(name="👥 members.json", value="⚠️ Fichier corrompu", inline=False)
        else:
            embed.add_field(name="👥 members.json", value="❌ Non généré", inline=False)

        # Prochain run
        now  = datetime.now()
        next_run = now.replace(hour=config["autoHour"], minute=config["autoMinute"], second=0, microsecond=0)
        if next_run <= now:
            next_run += timedelta(days=1)
        diff = next_run - now
        h, m = divmod(int(diff.total_seconds() // 60), 60)
        in_text = f"dans {h}h {m}min" if h else f"dans {m}min"
        embed.add_field(
            name="⏰ Prochaine génération auto",
            value=f"**{config['autoHour']:02d}:{config['autoMinute']:02d}** — {in_text}\n({next_run.strftime('%d/%m/%Y %H:%M')})",
            inline=False,
        )
        embed.add_field(name="📁 Dossier", value=f"`{OUTPUT_DIR}`", inline=False)

        await interaction.followup.send(embed=embed, ephemeral=True)

    # ══════════════════════════════════════════════════════════════════════════
    # /json-config (groupe)
    # ══════════════════════════════════════════════════════════════════════════
    @commands.slash_command(name="json-config", description="Configure la génération automatique des JSON")
    @commands.has_permissions(administrator=True)
    async def json_config_group(self, interaction: disnake.ApplicationCommandInteraction):
        pass

    # ── show ──────────────────────────────────────────────────────────────────
    @json_config_group.sub_command(name="show", description="Affiche la configuration actuelle")
    async def config_show(self, interaction: disnake.ApplicationCommandInteraction):
        await interaction.response.defer(ephemeral=True)
        config = load_config()

        srv_on  = ", ".join(k for k, v in config["serverFields"].items() if v)  or "_Aucun_"
        srv_off = ", ".join(k for k, v in config["serverFields"].items() if not v) or "_Aucun_"
        mbr_on  = ", ".join(k for k, v in config["memberFields"].items() if v)  or "_Aucun_"
        mbr_off = ", ".join(k for k, v in config["memberFields"].items() if not v) or "_Aucun_"

        embed = self._embed("⚙️ Configuration JSON Export", disnake.Color.blurple())
        embed.add_field(name="🕐 Génération auto", value=f"{config['autoHour']:02d}:{config['autoMinute']:02d} (heure serveur)", inline=True)
        embed.add_field(name="🎯 Serveur cible (members)", value=config["targetGuildId"] or "_Non défini_", inline=True)
        embed.add_field(name="\u200b", value="\u200b", inline=False)
        embed.add_field(name="✅ servers.json actifs",  value=srv_on,  inline=False)
        embed.add_field(name="❌ servers.json inactifs", value=srv_off, inline=False)
        embed.add_field(name="✅ members.json actifs",  value=mbr_on,  inline=False)
        embed.add_field(name="❌ members.json inactifs", value=mbr_off, inline=False)
        embed.add_field(name="📁 Dossier de sortie", value=f"`{OUTPUT_DIR}`", inline=False)

        await interaction.followup.send(embed=embed, ephemeral=True)

    # ── set-guild ────────────────────────────────────────────────────────────
    @json_config_group.sub_command(name="set-guild", description="Définit l'ID du serveur cible pour members.json")
    async def config_set_guild(self, interaction: disnake.ApplicationCommandInteraction, guild_id: str = commands.Param(description="ID du serveur Discord")):
        await interaction.response.defer(ephemeral=True)
        try:
            guild = self.bot.get_guild(int(guild_id)) or await self.bot.fetch_guild(int(guild_id))
            config = load_config()
            config["targetGuildId"] = guild_id
            save_config(config)
            embed = self._embed(
                "✅ Serveur cible défini", disnake.Color.green(),
                f"**{guild.name}** (`{guild_id}`) sera utilisé pour members.json.",
            )
        except Exception:
            embed = self._embed(
                "❌ Serveur introuvable", disnake.Color.red(),
                f"Le bot n'est pas dans le serveur `{guild_id}` ou l'ID est invalide.",
            )
        await interaction.followup.send(embed=embed, ephemeral=True)

    # ── set-schedule ─────────────────────────────────────────────────────────
    @json_config_group.sub_command(name="set-schedule", description="Définit l'heure de génération automatique quotidienne")
    async def config_set_schedule(self, interaction: disnake.ApplicationCommandInteraction, 
                                  heure: int = commands.Param(description="Heure (0-23)"), 
                                  minute: int = commands.Param(0, description="Minute (0-59, défaut: 0)")):
        await interaction.response.defer(ephemeral=True)
        if not (0 <= heure <= 23) or not (0 <= minute <= 59):
            await interaction.followup.send("❌ Heure invalide.", ephemeral=True)
            return
        config = load_config()
        config["autoHour"]   = heure
        config["autoMinute"] = minute
        save_config(config)
        embed = self._embed(
            "✅ Horaire mis à jour", disnake.Color.green(),
            f"Prochaine génération auto : **{heure:02d}:{minute:02d}** chaque jour.",
        )
        await interaction.followup.send(embed=embed)

    # ── server-field ─────────────────────────────────────────────────────────
    @json_config_group.sub_command(name="server-field", description="Active/désactive un champ dans servers.json")
    async def config_server_field(self, interaction: disnake.ApplicationCommandInteraction, 
                                  champ: str = commands.Param(description="Champ à modifier", choices=SERVER_FIELDS), 
                                  actif: bool = commands.Param(description="true = inclus, false = exclu")):
        await interaction.response.defer(ephemeral=True)
        config = load_config()
        config["serverFields"][champ] = actif
        save_config(config)
        state = "inclus ✅" if actif else "exclu ❌"
        embed = self._embed("✅ Champ servers.json mis à jour", disnake.Color.green(),
                            f"`{champ}` est maintenant **{state}** de servers.json.")
        await interaction.followup.send(embed=embed)

    # ── member-field ─────────────────────────────────────────────────────────
    @json_config_group.sub_command(name="member-field", description="Active/désactive un champ dans members.json")
    async def config_member_field(self, interaction: disnake.ApplicationCommandInteraction, 
                                  champ: str = commands.Param(description="Champ à modifier", choices=MEMBER_FIELDS), 
                                  actif: bool = commands.Param(description="true = inclus, false = exclu")):
        await interaction.response.defer(ephemeral=True)
        config = load_config()
        config["memberFields"][champ] = actif
        save_config(config)
        state = "inclus ✅" if actif else "exclu ❌"
        embed = self._embed("✅ Champ members.json mis à jour", disnake.Color.green(),
                            f"`{champ}` est maintenant **{state}** de members.json.")
        await interaction.followup.send(embed=embed)

    # ── reset ─────────────────────────────────────────────────────────────────
    @json_config_group.sub_command(name="reset", description="Remet la configuration par défaut")
    async def config_reset(self, interaction: disnake.ApplicationCommandInteraction):
        await interaction.response.defer(ephemeral=True)
        from src.json_export_service import CONFIG_FILE
        try:
            CONFIG_FILE.unlink(missing_ok=True)
        except Exception:
            pass
        load_config()  # recrée depuis DEFAULT_CONFIG
        embed = self._embed("✅ Configuration réinitialisée", disnake.Color.green(),
                            "Tous les paramètres ont été remis à leurs valeurs par défaut.")
        await interaction.followup.send(embed=embed)

    # ── set-output-dir ────────────────────────────────────────────────────────
    @json_config_group.sub_command(name="set-output-dir", description="Change le dossier de sortie des fichiers JSON")
    async def config_set_output_dir(self, interaction: disnake.ApplicationCommandInteraction, 
                                    chemin: str = commands.Param(description="Chemin absolu ou relatif vers le dossier (ex: /var/www/site/data)")):
        await interaction.response.defer(ephemeral=True)
        from pathlib import Path as _Path
        p = _Path(chemin)
        try:
            p.mkdir(parents=True, exist_ok=True)
            config = load_config()
            config["outputDir"] = str(p)
            save_config(config)
            embed = self._embed(
                "✅ Dossier de sortie mis à jour", disnake.Color.green(),
                f"Les JSON seront désormais écrits dans :\n`{p.resolve()}`",
            )
        except Exception as e:
            embed = self._embed("❌ Erreur", disnake.Color.red(), f"Impossible d'utiliser ce chemin :\n```{e}```")
        await interaction.followup.send(embed=embed)

    # ── add-founder-role ──────────────────────────────────────────────────────
    @json_config_group.sub_command(name="add-founder-role", description="Ajoute un nom de rôle reconnu comme 'fondateur'")
    async def config_add_founder_role(self, interaction: disnake.ApplicationCommandInteraction, 
                                      nom: str = commands.Param(description="Nom du rôle (insensible à la casse, correspondance partielle)")):
        await interaction.response.defer(ephemeral=True)
        config = load_config()
        roles  = config.get("founderRoles", [])
        nom_l  = nom.lower().strip()
        if nom_l in [r.lower() for r in roles]:
            embed = self._embed("ℹ️ Déjà présent", disnake.Color.yellow(),
                                f"`{nom}` est déjà dans la liste des rôles fondateurs.")
        else:
            roles.append(nom_l)
            config["founderRoles"] = roles
            save_config(config)
            embed = self._embed(
                "✅ Rôle fondateur ajouté", disnake.Color.green(),
                f"`{nom_l}` ajouté. Liste actuelle : {', '.join(f'`{r}`' for r in roles)}",
            )
        await interaction.followup.send(embed=embed)

    # ── remove-founder-role ───────────────────────────────────────────────────
    @json_config_group.sub_command(name="remove-founder-role", description="Retire un nom de rôle fondateur")
    async def config_remove_founder_role(self, interaction: disnake.ApplicationCommandInteraction, 
                                         nom: str = commands.Param(description="Nom du rôle à retirer")):
        await interaction.response.defer(ephemeral=True)
        config  = load_config()
        roles   = config.get("founderRoles", [])
        nom_l   = nom.lower().strip()
        updated = [r for r in roles if r.lower() != nom_l]
        if len(updated) == len(roles):
            embed = self._embed("ℹ️ Introuvable", disnake.Color.yellow(),
                                f"`{nom}` n'est pas dans la liste des rôles fondateurs.")
        else:
            config["founderRoles"] = updated
            save_config(config)
            reste = ', '.join(f'`{r}`' for r in updated) or "_Aucun_"
            embed = self._embed(
                "✅ Rôle fondateur retiré", disnake.Color.green(),
                f"`{nom_l}` retiré. Liste actuelle : {reste}",
            )
        await interaction.followup.send(embed=embed)

    # ══════════════════════════════════════════════════════════════════════════
    # /json-preview
    # ══════════════════════════════════════════════════════════════════════════
    @commands.slash_command(name="json-preview", description="Aperçu des données sans écrire les fichiers")
    @commands.has_permissions(administrator=True)
    async def json_preview(self, interaction: disnake.ApplicationCommandInteraction, 
                           cible: str = commands.Param("servers", description="Quoi prévisualiser ?", choices={"Serveurs": "servers", "Membres": "members"}), 
                           limite: int = commands.Param(3, description="Nombre d'éléments à afficher (1-10, défaut: 3)")):
        await interaction.response.defer(ephemeral=True)
        limite = max(1, min(limite, 10))  # entre 1 et 10

        try:
            embed = self._embed(f"🔍 Aperçu — {cible}", disnake.Color.blurple())
            embed.set_footer(text="Aperçu uniquement — aucun fichier écrit")

            if cible == "servers":
                data = await preview_servers(self.bot, limit=limite)
                for s in data:
                    val = f"ID: `{s['id']}`"
                    if "memberCount" in s:
                        val += f"\nMembres: {s['memberCount']}"
                    if s.get("icon"):
                        val += f"\nIcon: {s['icon']}"
                    embed.add_field(name=f"🌐 {s['name']}", value=val, inline=False)

            else:  # members
                data = await preview_members(self.bot, limit=limite)
                embed.description = f"Serveur : **{data['guildName']}**"
                for m in data["members"]:
                    roles_str = ", ".join(m["roles"][:5]) or "_Aucun_"
                    if len(m["roles"]) > 5:
                        roles_str += f" +{len(m['roles'])-5}"
                    embed.add_field(
                        name=f"👤 {m['displayName']} ({m['username']})",
                        value=f"ID: `{m['id']}`\nRôles: {roles_str}",
                        inline=False,
                    )

        except Exception as e:
            embed = self._embed("❌ Erreur", disnake.Color.red(), f"```{e}```")

        await interaction.followup.send(embed=embed, ephemeral=True)

    # ══════════════════════════════════════════════════════════════════════════
    # /json-history
    # ══════════════════════════════════════════════════════════════════════════
    @commands.slash_command(name="json-history", description="Historique des dernières générations automatiques et manuelles")
    @commands.has_permissions(administrator=True)
    async def json_history(self, interaction: disnake.ApplicationCommandInteraction, 
                           limite: int = commands.Param(5, description="Nombre d'entrées à afficher (défaut: 5, max: 10)")):
        await interaction.response.defer(ephemeral=True)
        limite  = max(1, min(limite, 10))
        history = get_history()

        embed = self._embed("📜 Historique des générations JSON", disnake.Color.blurple())

        if not history:
            embed.description = "_Aucune génération enregistrée pour l'instant._"
        else:
            for entry in history[:limite]:
                date_str = datetime.fromisoformat(entry["date"]).strftime("%d/%m/%Y %H:%M")
                trigger  = "🤖 Auto" if entry.get("trigger") == "auto" else "👤 Manuel"
                status   = "✅" if entry.get("success") else "⚠️"

                lines = [f"{trigger} — {date_str}"]
                if entry.get("serversCount") is not None:
                    lines.append(f"📋 {entry['serversCount']} serveurs")
                if entry.get("membersCount") is not None:
                    lines.append(f"👥 {entry['membersCount']} membres ({entry.get('guildName', '?')})")
                if entry.get("errors"):
                    lines.append(f"❌ Erreurs: {'; '.join(entry['errors'])}")

                embed.add_field(
                    name=f"{status} Génération du {date_str}",
                    value="\n".join(lines),
                    inline=False,
                )

        await interaction.followup.send(embed=embed, ephemeral=True)


# ─── Setup ───────────────────────────────────────────────────────────────────
def setup(bot):
    bot.add_cog(WebsiteSync(bot))
    logger.info("Cog WebsiteSync chargé.")
