# modules/pirate_report/pirate_report.py
import re
import time
from datetime import datetime, timezone
from urllib.parse import urlparse
import discord
from discord.ext import commands
from discord import app_commands
from modules.common.settings import cfg

from mod_perms import (
    is_moderator_member,
    is_moderator_userid,
    require_mod_ctx,
    require_mod_interaction,
)

CHECK = '✅'
CROSS = '❌'

# --------------------- Helpers ---------------------

_ACCT_RE = re.compile(r'.+#\d{5}$')

DISCORD_MEDIA_HOSTS = {"cdn.discordapp.com", "media.discordapp.net"}
MEDIA_EXTS_IMAGE = {".png", ".jpg", ".jpeg", ".gif", ".webp"}
MEDIA_EXTS_VIDEO = {".mp4", ".webm", ".mov"}
MEDIA_EXTS_ALL = MEDIA_EXTS_IMAGE | MEDIA_EXTS_VIDEO


def _acct_ok(s: str) -> bool:
    return bool(_ACCT_RE.fullmatch(s.strip()))


def _now_utc_str() -> str:
    return datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M UTC')


def _parse_bool(s: str) -> bool:
    v = s.strip().lower()
    if v in ('y', 'yes', 'true', 't', '1'):
        return True
    if v in ('n', 'no', 'false', 'f', '0'):
        return False
    raise ValueError("Please enter yes or no")


def _classify_discord_media(url: str):
    """
    Return ('image'|'video', normalized_url) if valid Discord CDN media; else (None, reason).
    We only accept direct CDN links so media can render inline without leaving the channel.
    """
    try:
        u = url.strip()
        if not u:
            return (None, "Empty URL.")
        pr = urlparse(u)
        if pr.scheme not in ("http", "https"):
            return (None, "URL must start with http:// or https://")
        host = pr.netloc.lower()
        if host not in DISCORD_MEDIA_HOSTS:
            return (None, "URL must be a **Discord media** link (cdn.discordapp.com or media.discordapp.net).")
        path = pr.path or ""
        dot = path.rfind(".")
        if dot == -1:
            return (None, "URL must end with a known media file extension.")
        ext = path[dot:].lower()
        if ext not in MEDIA_EXTS_ALL:
            return (None, f"Unsupported media type `{ext}`. Allowed: images {sorted(MEDIA_EXTS_IMAGE)}, videos {sorted(MEDIA_EXTS_VIDEO)}.")
        kind = "image" if ext in MEDIA_EXTS_IMAGE else "video"
        return (kind, u)
    except Exception:
        return (None, "Invalid URL format.")


def _jump_url(guild_id: int, channel_id: int, message_id: int) -> str:
    return f"https://discord.com/channels/{guild_id}/{channel_id}/{message_id}"


def _make_report_embed(title: str, color: discord.Color, report_dict: dict, include_status: bool = False):
    e = discord.Embed(title=title, color=color, timestamp=datetime.utcnow())
    e.add_field(name="Character", value=report_dict['character_name'], inline=False)
    e.add_field(name="Account", value=report_dict['account_name'], inline=False)
    e.add_field(name="Submitted by", value=f"<@{report_dict['submitter_id']}>", inline=False)

    proof_url = (report_dict.get('proof_url') or "").strip()
    proof_type = (report_dict.get('proof_type') or "").strip()

    if include_status:
        e.add_field(
            name="Status",
            value=report_dict.get('status_line', 'Pending'),
            inline=False
        )

    # Show proof inline if it's an image; for videos we'll keep the URL in message content.
    if proof_url and proof_type == 'image':
        e.set_image(url=proof_url)

    # Add a compact proof field for quick visibility (always safe to include)
    e.add_field(
        name="Proof",
        value=proof_url if proof_url else "_No proof provided — strongly encouraged._",
        inline=False
    )

    # A small hint footer
    e.set_footer(text="Attach proof when possible to speed up moderation.")
    return e


# --------------------- Views ----------------------

class ReportJumpView(discord.ui.View):
    """Simple link button to jump to the user's original ack message."""
    def __init__(self, url: str):
        super().__init__(timeout=None)
        if url:
            self.add_item(discord.ui.Button(label="Jump to message", style=discord.ButtonStyle.link, url=url))


# --------------------- Modals ----------------------

class ReportModal(discord.ui.Modal, title="Submit Pirate Report"):
    def __init__(self, cog: "PirateReportCog"):
        super().__init__()
        self.cog = cog

        self.character_name = discord.ui.TextInput(
            label="In-game nickname",
            placeholder="e.g., SandStalker",
            max_length=64,
            required=True
        )
        self.account_name = discord.ui.TextInput(
            label="Account (Name#12345)",
            placeholder="Must end with # and 5 digits",
            max_length=64,
            required=True
        )
        self.proof_url = discord.ui.TextInput(
            label="Proof (Discord media URL — optional)",
            placeholder="Direct Discord CDN link to image/video (highly encouraged, but optional)",
            required=False,
            max_length=300
        )

        self.add_item(self.character_name)
        self.add_item(self.account_name)
        self.add_item(self.proof_url)

    async def on_submit(self, interaction: discord.Interaction):
        if not interaction.guild:
            return await interaction.response.send_message("Use this in a server.", ephemeral=True)

        char = self.character_name.value.strip()
        acct = self.account_name.value.strip()
        proof_raw = (self.proof_url.value or "").strip()

        if not _acct_ok(acct):
            return await interaction.response.send_message(
                "❌ Invalid account format. It must end with `#` and **five digits** (e.g. `SomeUser#12345`).",
                ephemeral=True
            )

        proof_type = ""
        proof_val = ""
        if proof_raw:
            pt, pv = _classify_discord_media(proof_raw)
            if pt is None:
                return await interaction.response.send_message(f"❌ Invalid proof link: {pv}", ephemeral=True)
            proof_type, proof_val = pt, pv  # valid

        dm = self.cog.bot.data_manager
        char_l = char.lower()
        acct_l = acct.lower()

        # Anti-spam: 60s per reporter
        now = time.time()
        recent = [r for r in dm.get('reports')
                  if r.get('submitter_id') == interaction.user.id and (now - r.get('ts', now)) < 60]
        if recent:
            return await interaction.response.send_message("⏱️ Please wait 60 seconds before submitting another report.", ephemeral=True)

        # Already approved?
        if any(p['account_name'].lower() == acct_l for p in dm.get('pirates')):
            return await interaction.response.send_message("❌ This player is already in the pirate list.", ephemeral=True)

        # Pending duplicate?
        if any(r['character_name'].lower() == char_l and r['account_name'].lower() == acct_l for r in dm.get('reports')):
            return await interaction.response.send_message("⏳ A report for this player is already pending moderation.", ephemeral=True)

        # Acknowledge in channel so we can edit later when approved/rejected
        try:
            ack = await interaction.channel.send(f"{interaction.user.mention} thanks — your report was sent to moderators for review.")
        except Exception as e:
            print("[pirate_report] ack send failed:", repr(e))
            ack = None

        # Send to mod channel with ✅/❌, including inline media if present
        mod_ch = interaction.guild.get_channel(self.cog.mod_channel)
        if not mod_ch:
            return await interaction.response.send_message("❌ Mod channel not configured.", ephemeral=True)

        report_payload = {
            'character_name': char,
            'account_name': acct,
            'submitter_id': interaction.user.id,
            'proof_url': proof_val,
            'proof_type': proof_type,
        }

        # Build jump button (if ack exists)
        view = None
        if ack:
            try:
                url = _jump_url(interaction.guild.id, interaction.channel.id, ack.id)
                view = ReportJumpView(url)
            except Exception:
                view = None

        try:
            embed = _make_report_embed("🚩 Pirate Report", discord.Color.orange(), report_payload, include_status=False)
            # Video must be in message content for inline player; images live in the embed
            content = proof_val if proof_type == 'video' else None
            mod_msg = await mod_ch.send(content=content, embed=embed, view=view)
            await mod_msg.add_reaction(CHECK)
            await mod_msg.add_reaction(CROSS)
        except Exception as e:
            print("[pirate_report] mod message failed:", repr(e))
            return await interaction.response.send_message("❌ Failed to send to mod channel.", ephemeral=True)

        dm.add('reports', {
            'report_id': mod_msg.id,
            'guild_id': interaction.guild.id,
            'character_name': char,
            'account_name': acct,
            'submitter_id': interaction.user.id,
            'origin_channel_id': interaction.channel.id if interaction.channel else 0,
            'ack_message_id': ack.id if ack else 0,
            'status': 'pending',
            'ts': now,
            'proof_url': proof_val,
            'proof_type': proof_type,
        })

        await interaction.response.send_message("✅ Report submitted to moderators.", ephemeral=True)


class EditPirateModal(discord.ui.Modal, title="Edit Pirate Entry"):
    def __init__(self, cog: "PirateReportCog"):
        super().__init__()
        self.cog = cog

        self.old_account = discord.ui.TextInput(
            label="Current account (Name#12345)",
            placeholder="Exact current account in the list",
            max_length=64,
            required=True
        )
        self.new_character = discord.ui.TextInput(
            label="New in-game nickname (optional)",
            placeholder="Leave blank to keep existing",
            max_length=64,
            required=False
        )
        self.new_account = discord.ui.TextInput(
            label="New account (optional, Name#12345)",
            placeholder="Leave blank to keep existing",
            max_length=64,
            required=False
        )

        self.add_item(self.old_account)
        self.add_item(self.new_character)
        self.add_item(self.new_account)

    async def on_submit(self, interaction: discord.Interaction):
        if not interaction.guild:
            return await interaction.response.send_message("Use this in a server.", ephemeral=True)

        if not is_moderator_member(interaction.user, self.cog.bot):
            return await interaction.response.send_message("This action is restricted to moderators.", ephemeral=True)

        old_acct = self.old_account.value.strip()
        new_char = (self.new_character.value or "").strip()
        new_acct = (self.new_account.value or "").strip()

        if not new_char and not new_acct:
            return await interaction.response.send_message(
                "❌ Provide **at least one** of: new nickname or new account.",
                ephemeral=True
            )

        dm = self.cog.bot.data_manager
        pirates = dm.get('pirates')
        rec = next((p for p in pirates if p['account_name'].lower() == old_acct.lower()), None)
        if not rec:
            return await interaction.response.send_message("❌ Pirate not found.", ephemeral=True)

        if new_acct:
            if not _acct_ok(new_acct):
                return await interaction.response.send_message("❌ Invalid new account format (Name#12345).", ephemeral=True)
            if new_acct.lower() != old_acct.lower() and any(p['account_name'].lower() == new_acct.lower() for p in pirates):
                return await interaction.response.send_message("❌ Another pirate already has that account.", ephemeral=True)

        before = rec.copy()
        dm.remove('pirates', lambda p: p['account_name'].lower() == old_acct.lower())
        if new_char:
            rec['character_name'] = new_char
        if new_acct:
            rec['account_name'] = new_acct
        dm.add('pirates', rec)

        await self.cog._modlog(
            interaction.guild,
            f"✏️ Edited pirate by {interaction.user.mention}: "
            f"{'name ' + before['character_name'] + ' → ' + rec['character_name'] if new_char else ''} "
            f"{'/ account ' + before['account_name'] + ' → ' + rec['account_name'] if new_acct else ''}".strip()
        )
        await interaction.response.send_message("✅ Pirate updated.", ephemeral=True)
        await self.cog._refresh_pirates_list(interaction.guild)


class EncounterModal(discord.ui.Modal, title="Log Pirate Encounter"):
    def __init__(self, cog: "PirateReportCog"):
        super().__init__()
        self.cog = cog

        self.identifier = discord.ui.TextInput(
            label="Pirate (name or account)",
            placeholder="MuadDib or MuadDib#12345",
            required=True,
            max_length=64
        )
        self.group_size = discord.ui.TextInput(
            label="Group size (including this pirate)",
            placeholder="How many pirates were in the group?",
            required=True
        )
        self.kills = discord.ui.TextInput(
            label="Kills (integer ≥ 0; 0 = none/unknown)",
            placeholder="How many did the pirate kill during the encounter",
            required=True
        )
        self.destruction = discord.ui.TextInput(
            label="Destructive? (yes/no)",
            placeholder="Did they destroy a base/ornithopter? yes or no",
            required=True
        )
        self.skill = discord.ui.TextInput(
            label="Perceived Skill (0–5, 0 = unknown)",
            placeholder="0..5",
            required=True
        )

        self.add_item(self.identifier)
        self.add_item(self.group_size)
        self.add_item(self.kills)
        self.add_item(self.destruction)
        self.add_item(self.skill)

    async def on_submit(self, interaction: discord.Interaction):
        ident_raw = self.identifier.value.strip()
        guild = interaction.guild
        if guild is None:
            return await interaction.response.send_message("This can only be used in a server.", ephemeral=True)

        # Parse numeric fields
        try:
            group_size = int(self.group_size.value.strip()); assert group_size >= 1
        except Exception:
            return await interaction.response.send_message("❌ **Group size** must be an integer ≥ 1.", ephemeral=True)
        try:
            kills = int(self.kills.value.strip()); assert kills >= 0
        except Exception:
            return await interaction.response.send_message("❌ **Kills** must be an integer ≥ 0.", ephemeral=True)
        try:
            destruction = _parse_bool(self.destruction.value)
        except ValueError:
            return await interaction.response.send_message("❌ **Destructive** must be **yes** or **no**.", ephemeral=True)
        try:
            skill = int(self.skill.value.strip()); assert 0 <= skill <= 5
        except Exception:
            return await interaction.response.send_message("❌ **Skill** must be an integer between **0** and **5**.", ephemeral=True)

        # Resolve to a single pirate record and canonical account identifier
        pirate, error = self.cog.resolve_pirate(ident_raw)
        if error:
            return await interaction.response.send_message(error, ephemeral=True)
        if not pirate:
            return await interaction.response.send_message(
                "❌ No such pirate registered. Ask a moderator to add them, or submit a **/report** for review.",
                ephemeral=True
            )

        canonical_acct = pirate['account_name']                # <- always store account here
        acct_l = canonical_acct.lower()
        char_l = pirate['character_name'].lower()

        dm = self.cog.bot.data_manager
        now = time.time()

        # Rate-limit by canonical account
        recent = [
            e for e in dm.get('encounters')
            if e['reporter_id'] == interaction.user.id
            and e.get('identifier','').lower() in (acct_l, char_l)  # respect legacy
            and (now - e['timestamp']) < 600
        ]
        if recent:
            return await interaction.response.send_message(
                "⏱️ You can only report the same pirate once every **10 minutes**.",
                ephemeral=True
            )

        # Store with canonical account as identifier
        rec = {
            'identifier': canonical_acct,
            'reporter_id': interaction.user.id,
            'timestamp': now,
            'group_size': group_size,
            'kills': kills,
            'destruction': destruction,
            'skill': skill
        }
        dm.add('encounters', rec)

        # Recalculate threat (count both new account-based and legacy name-based entries)
        encs = [e for e in dm.get('encounters') if e.get('identifier','').lower() in (acct_l, char_l)]
        total = len(encs)
        if total:
            group_threshold = self.cog.group_threshold
            kill_rate = sum(e['kills'] > 0 for e in encs) / total
            group_rate = sum(e['group_size'] >= group_threshold for e in encs) / total
            destroy_rate = sum(bool(e['destruction']) for e in encs) / total
            skill_samples = [e['skill'] / 5 for e in encs if e['skill'] > 0]
            skill_rate = (sum(skill_samples) / len(skill_samples)) if skill_samples else 0.0

            w_k, w_d, w_g, w_s = self.cog.w_kill, self.cog.w_destruction, self.cog.w_group, self.cog.w_skill
            weighted = (w_k * kill_rate) + (w_d * destroy_rate) + (w_g * group_rate) + (w_s * skill_rate)
            threat_level = int(round(100 * min(1.0, max(0.0, weighted))))

            # Update pirate record
            pirates = dm.get('pirates')
            def match(p): return p['account_name'].lower() == acct_l
            rec0 = next((p for p in pirates if match(p)), None)
            if rec0:
                dm.remove('pirates', match)
                rec0.update({'threat_level': threat_level, 'encounter_count': total})
                dm.add('pirates', rec0)

        await interaction.response.send_message(
            f"✅ Encounter recorded for **{canonical_acct}**.",
            ephemeral=True
        )

        await self.cog._refresh_pirates_list(interaction.guild)


# -------------- Cog: commands + listeners ---------------

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

        # Channels
        self.mod_channel = r.int('mod_channel_id', 0)
        self.modlog_channel_id = r.int('modlog_channel_id', 0)

        # Threat config
        self.group_threshold = r.int('threat_group_threshold', 3)

        def _f(key: str, default: float) -> float:
            try:
                return float(r.get(key, str(default)))
            except Exception:
                return default

        self.w_kill = _f('threat_w_kill', 0.35)
        self.w_destruction = _f('threat_w_destruction', 0.30)
        self.w_group = _f('threat_w_group', 0.20)
        self.w_skill = _f('threat_w_skill', 0.15)

    async def _refresh_pirates_list(self, guild: discord.Guild):
        plist = self.bot.get_cog('PiratesListCog')
        if plist:
            await plist.refresh_list(guild)

    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)
            except Exception as e:
                print("[pirate_report] modlog send failed:", repr(e))
        self.bot.data_manager.add('modlog', {'ts': time.time(), 'guild_id': guild.id, 'content': content})

    # --- Resolver: name/account -> pirate record (handles ambiguity) ---
    def resolve_pirate(self, ident: str):
        """Return (pirate_record or None, error_message or None)."""
        dm = self.bot.data_manager
        pirates = dm.get('pirates')
        s = ident.strip()
        s_l = s.lower()
        if '#' in s and _acct_ok(s):
            rec = next((p for p in pirates if p['account_name'].lower() == s_l), None)
            return (rec, None)
        # resolve by character; must be unambiguous
        matches = [p for p in pirates if p['character_name'].lower() == s_l]
        if not matches:
            return (None, None)
        if len(matches) > 1:
            return (None, "❌ That character name is **ambiguous**. Please use the account (e.g. `Name#12345`).")
        return (matches[0], None)

    # Remove pirate (mod-only)
    @app_commands.command(name="remove_pirate", description="[MOD] Remove an approved pirate entry")
    @app_commands.describe(account_name="Account name to remove")
    async def remove_pirate(self, interaction: discord.Interaction, account_name: str):
        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

        acct_lower = account_name.strip().lower()
        dm = self.bot.data_manager
        if not any(p['account_name'].lower() == acct_lower for p in dm.get('pirates')):
            return await interaction.response.send_message("Pirate not found.", ephemeral=True)

        dm.remove('pirates', lambda p: p['account_name'].lower() == acct_lower)
        await self._modlog(interaction.guild, f"🗑️ Removed pirate {account_name} by {interaction.user.mention}")
        await self._refresh_pirates_list(interaction.guild)
        await interaction.response.send_message("Removed.", ephemeral=True)

    # Modal launchers
    @app_commands.command(name="report", description="Submit a pirate report for moderator review (opens a form)")
    async def report(self, interaction: discord.Interaction):
        if not interaction.guild:
            return await interaction.response.send_message("Use this in a server.", ephemeral=True)
        await interaction.response.send_modal(ReportModal(self))

    @app_commands.command(name="edit_pirate", description="[MOD] Edit a pirate entry (opens a form)")
    async def edit_pirate(self, interaction: discord.Interaction):
        if not await require_mod_interaction(interaction):
            return
        await interaction.response.send_modal(EditPirateModal(self))

    @app_commands.command(name="encounter", description="Log a pirate encounter (opens a form)")
    async def encounter(self, interaction: discord.Interaction):
        if not interaction.guild:
            return await interaction.response.send_message("Use this in a server.", ephemeral=True)
        await interaction.response.send_modal(EncounterModal(self))

    # ---- Migration: convert encounter identifiers to accounts (mod-only) ----
    @app_commands.command(name="encounters_migrate_ids", description="[MOD] Migrate encounter identifiers to account names")
    async def encounters_migrate_ids(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)

        dm = self.bot.data_manager
        pirates = dm.get('pirates')
        by_char = {}
        for p in pirates:
            by_char.setdefault(p['character_name'].lower(), []).append(p)
        by_acct = {p['account_name'].lower(): p for p in pirates}

        changed = ambiguous = missing = already = 0

        for e in dm.get('encounters'):
            ident = e.get('identifier', '')
            if not ident:
                continue
            if '#' in ident:
                already += 1
                continue
            key = ident.lower()
            matches = by_char.get(key, [])
            if not matches:
                missing += 1
                continue
            if len(matches) > 1:
                ambiguous += 1
                continue
            acct = matches[0]['account_name']

            def pred(x, ts=e['timestamp'], rid=e['reporter_id'], ident_old=ident):
                return x.get('timestamp') == ts and x.get('reporter_id') == rid and x.get('identifier') == ident_old
            def upd(x, acct_new=acct):
                x['identifier'] = acct_new
                return x

            ok = dm.update('encounters', pred, upd)
            if ok:
                changed += 1

        await interaction.followup.send(
            "Migration complete.\n"
            f"- Updated to accounts: **{changed}**\n"
            f"- Already accounts: **{already}**\n"
            f"- Ambiguous character names (skipped): **{ambiguous}**\n"
            f"- Not found in pirates list (skipped): **{missing}**",
            ephemeral=True
        )

        await self._refresh_pirates_list(interaction.guild)

    # Moderator reaction handling (atomic claim)
    @commands.Cog.listener()
    async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent):
        if (
            payload.channel_id != self.mod_channel
            or str(payload.emoji) not in (CHECK, CROSS)
            or payload.user_id == self.bot.user.id
        ):
            return

        channel = self.bot.get_channel(payload.channel_id)
        if not channel or not channel.guild:
            return
        if not is_moderator_userid(channel.guild, payload.user_id, self.bot):
            return

        try:
            msg = await channel.fetch_message(payload.message_id)
        except Exception as e:
            print("[pirate_report] fetch mod msg failed:", repr(e))
            return

        dm = self.bot.data_manager

        # Atomically claim this report
        claimed = dm.update(
            'reports',
            lambda r: r.get('report_id') == msg.id and r.get('status') == 'pending',
            lambda r: (r.update({'status': 'deciding'}), r)[1]
        )
        if not claimed:
            return

        report = next((r for r in dm.get('reports') if r.get('report_id') == msg.id), None)
        if not report:
            return

        approved = (str(payload.emoji) == CHECK)
        guild = channel.guild
        stamp = _now_utc_str()

        # Build new embed + content (keep proof visible if present)
        status_text = f"{'Approved' if approved else 'Rejected'} by <@{payload.user_id}> on {stamp}"
        report_view = {
            'character_name': report['character_name'],
            'account_name': report['account_name'],
            'submitter_id': report['submitter_id'],
            'proof_url': report.get('proof_url', ''),
            'proof_type': report.get('proof_type', ''),
            'status_line': status_text,
        }
        color = discord.Color.green() if approved else discord.Color.red()
        title = "✅ Pirate Report — Approved" if approved else "❌ Pirate Report — Rejected"
        new_embed = _make_report_embed(title, color, report_view, include_status=True)

        # For videos, ensure the URL stays in message content so the inline player remains visible.
        new_content = report_view['proof_url'] if report_view.get('proof_type') == 'video' else None

        # Always (re)attach jump button if we have the ack info
        view = None
        try:
            if report.get('origin_channel_id') and report.get('ack_message_id'):
                url = _jump_url(guild.id, report['origin_channel_id'], report['ack_message_id'])
                view = ReportJumpView(url)
        except Exception:
            view = None

        try:
            await msg.clear_reactions()
        except Exception as e:
            print("[pirate_report] clear reactions failed:", repr(e))
        try:
            await msg.edit(content=new_content, embed=new_embed, view=view)
        except Exception as e:
            print("[pirate_report] edit mod msg failed:", repr(e))

        if approved:
            acct_lower = report['account_name'].lower()
            if not any(p['account_name'].lower() == acct_lower for p in dm.get('pirates')):
                dm.add('pirates', {
                    'character_name': report['character_name'],
                    'account_name': report['account_name'],
                    'submitter': str(report['submitter_id']),
                    'threat_level': 0,
                    'encounter_count': 0,
                    'added_ts': time.time()
                })
                await self._refresh_pirates_list(guild)

        await self._modlog(guild, f"{'✅ Approved' if approved else '❌ Rejected'} report for {report['character_name']} ({report['account_name']}) by <@{payload.user_id}>.")

        try:
            if report.get('origin_channel_id') and report.get('ack_message_id'):
                ch = guild.get_channel(report['origin_channel_id'])
                if ch:
                    ack_msg = await ch.fetch_message(report['ack_message_id'])
                    await ack_msg.edit(content=f"{('✅ Approved' if approved else '❌ Rejected')} pirate report of {report['character_name']} ({report['account_name']}) by <@{payload.user_id}> on {stamp}.")
        except Exception as e:
            print("[pirate_report] edit ack msg failed:", repr(e))

        dm.remove('reports', lambda r: r.get('report_id') == msg.id)


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