# modules/pirate_cards/pirate_cards.py
import asyncio
import discord
from discord.ext import commands
from discord import app_commands
from datetime import datetime
from mod_perms import require_mod_ctx, require_mod_interaction
from modules.common.settings import cfg  # ENV-first config helper


class PirateCardsCog(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
        r = cfg(bot)

        # IDs / config (ENV -> optional INI fallback)
        self.pirates_channel_id = r.int('pirates_list_channel_id', 0)
        self.modlog_channel_id = r.int('modlog_channel_id', 0)

        # thresholds / samples (with defaults)
        self.group_threshold = r.int('threat_group_threshold', 3)
        self.min_samples = r.int('threat_min_samples_for_stats', 3)

        # safe posting (don’t ping)
        self._no_mentions = discord.AllowedMentions.none()
        # serialize rebuilds per guild
        self._locks: dict[int, asyncio.Lock] = {}

    # -------- internals / helpers --------
    def _lock_for(self, guild_id: int) -> asyncio.Lock:
        self._locks.setdefault(guild_id, asyncio.Lock())
        return self._locks[guild_id]

    @staticmethod
    def _esc(s: str) -> str:
        """Escape markdown & neutralize mentions for any user-sourced strings we show."""
        safe = discord.utils.escape_markdown(str(s))
        return safe.replace("@", "@\u200b")

    def _color_for_threat(self, threat: int, enc_count: int) -> discord.Color:
        """
        Gradient:
          - If too few samples: dark gray (unknown)
          - Else 0 -> green (0,255,0), 100 -> red (255,0,0)
        """
        if enc_count < self.min_samples:
            return discord.Color.dark_gray()

        t = max(0, min(100, int(threat))) / 100.0
        r = int(round(255 * t))
        g = int(round(255 * (1.0 - t)))
        return discord.Color.from_rgb(r, g, 0)

    def _bucket(self, rate: float, samples: int) -> str:
        if samples < self.min_samples:
            return "unknown"
        if rate <= 0:
            return "never"
        if rate <= 0.25:
            return "rarely"
        if rate <= 0.60:
            return "sometimes"
        if rate <= 0.85:
            return "often"
        return "always"

    def _encounters_for(self, pirate: dict):
        acct_l = str(pirate.get('account_name', '')).lower()
        char_l = str(pirate.get('character_name', '')).lower()
        out = []
        for e in self.bot.data_manager.get('encounters'):
            try:
                ident = str(e.get('identifier', '')).lower()
                if ident in (acct_l, char_l):
                    out.append(e)
            except Exception:
                continue
        return out

    def _get_card_record(self, account_lower: str):
        for r in self.bot.data_manager.get('pirate_cards'):
            if r.get('account_lower') == account_lower:
                return r
        return None

    async def _modlog(self, guild: discord.Guild, content: str):
        if not self.modlog_channel_id:
            return
        ch = guild.get_channel(self.modlog_channel_id)
        if ch:
            try:
                await ch.send(content, allowed_mentions=self._no_mentions)
            except Exception:
                pass

    async def _build_embed(self, pirate: dict) -> discord.Embed:
        encs = self._encounters_for(pirate)
        total = len(encs)

        def _i(v, d=0):
            try:
                return int(v)
            except Exception:
                return d

        group_rate = (sum(1 for e in encs if _i(e.get('group_size'), 0) >= self.group_threshold) / total) if total else 0.0
        destroy_rate = (sum(1 for e in encs if bool(e.get('destruction'))) / total) if total else 0.0

        # last encountered: date only (UTC)
        last_date = "—"
        if total:
            try:
                last_ts = max(int(float(e.get('timestamp', 0))) for e in encs)
                last_date = datetime.utcfromtimestamp(last_ts).strftime('%Y-%m-%d')
            except Exception:
                last_date = "—"

        groups_str = self._bucket(group_rate, total)
        destr_str = self._bucket(destroy_rate, total)

        # threat & color
        threat = int(pirate.get('threat_level', 0))
        color = self._color_for_threat(threat, total)

        char = self._esc(pirate.get('character_name', 'Unknown'))
        acct = self._esc(pirate.get('account_name', 'Unknown#00000'))

        embed = discord.Embed(title=char, color=color)
        embed.add_field(name="Account name", value=f"*{acct}*", inline=False)
        embed.add_field(name="Threat", value=f"{threat}%", inline=True)
        embed.add_field(name="In groups", value=groups_str, inline=True)
        embed.add_field(name="Destructive", value=destr_str, inline=True)
        embed.set_footer(text=f"Encounters: {total} | Last: {last_date}")
        return embed

    async def refresh_card_for_account(self, guild: discord.Guild, account_name: str):
        """Create or update a single pirate card by account name."""
        acct_l = str(account_name or "").lower()
        pirates = self.bot.data_manager.get('pirates')
        p = next((x for x in pirates if str(x.get('account_name', '')).lower() == acct_l), None)
        if not p:
            return

        channel = guild.get_channel(self.pirates_channel_id)
        if not channel:
            return

        embed = await self._build_embed(p)
        rec = self._get_card_record(acct_l)

        if rec:
            try:
                msg = await channel.fetch_message(int(rec['message_id']))
                await msg.edit(embed=embed)
                return
            except Exception:
                # fall-through to recreate
                pass

        try:
            msg = await channel.send(embed=embed, allowed_mentions=self._no_mentions)
            self.bot.data_manager.add('pirate_cards', {
                'account_lower': acct_l,
                'message_id': msg.id
            })
        except Exception:
            pass

    async def delete_card_for_account(self, guild: discord.Guild, account_name: str):
        """Delete a pirate card by account and remove its record."""
        acct_l = str(account_name or "").lower()
        channel = guild.get_channel(self.pirates_channel_id)
        rec = self._get_card_record(acct_l)

        if rec and channel:
            try:
                msg = await channel.fetch_message(int(rec['message_id']))
                await msg.delete()
            except Exception:
                pass

        # ensure record is gone regardless
        self.bot.data_manager.remove('pirate_cards', lambda r: r.get('account_lower') == acct_l)

    async def update_on_edit(self, guild: discord.Guild, old_account: str, new_account: str):
        """When an account name changes, carry over the card mapping and refresh."""
        old_l = str(old_account or "").lower()
        new_l = str(new_account or "").lower()
        rec = self._get_card_record(old_l)
        if rec:
            # replace mapping to new key
            self.bot.data_manager.remove('pirate_cards', lambda r: r.get('account_lower') == old_l)
            self.bot.data_manager.add('pirate_cards', {
                'account_lower': new_l,
                'message_id': rec['message_id']
            })
        await self.refresh_card_for_account(guild, new_account)

    # -------- command (mod-gated via require_mod_ctx) --------
    @app_commands.command(name="pirate_cards_rebuild", description="[MOD] Rebuild pirate cards for all known pirates")
    async def pirate_cards_rebuild(self, interaction: discord.Interaction):
        if not interaction.guild:
            return await interaction.response.send_message("Use this in a server.", ephemeral=True)
        if not await require_mod_interaction(interaction, "This command is restricted to moderators."):
            return

        await interaction.response.defer(ephemeral=True)
        guild = interaction.guild
        ch = guild.get_channel(self.pirates_channel_id)
        if not ch:
            return await interaction.followup.send("Configured pirates_list_channel_id not found.", ephemeral=True)

        count = 0
        for p in self.bot.data_manager.get('pirates'):
            try:
                await self.refresh_card_for_account(guild, p.get('account_name', ''))
                count += 1
            except Exception:
                continue

        await interaction.followup.send(f"Rebuilt/updated {count} pirate cards.", ephemeral=True)

async def setup(bot):
    await bot.add_cog(PirateCardsCog(bot))
