# modules/reaction_role/reaction_role.py
import discord
from discord.ext import commands
from discord import app_commands
from modules.common.emoji_accept import is_accept
from modules.common.settings import cfg  # ENV-first helper

CHECKMARK = '✅'

class ReactionRoleCog(commands.Cog):
    """
    Records agreements and manages Full Access.
    Nickname flow:
      • Add accept on nickname message -> atomically mark agreed + pending and open ONE review
      • Remove accept on nickname message -> clear only if user has no accept reactions left
    Full Access: granted when Rules ✅ + RoE ✅ + Nickname *claimed* (pending or verified).
    Revoked when any of the three is missing.
    """

    def __init__(self, bot):
        self.bot = bot
        r = cfg(bot)

        # Message + role IDs from ENV/INI (default 0 if unset)
        self.rules_msg_id = r.int('rules_message_id', 0)
        self.engage_msg_id = r.int('engagement_message_id', 0)
        self.nick_msg_id = r.int('nickname_message_id', 0)

        self.rules_role = r.int('rules_role_id', 0)
        self.engage_role = r.int('engagement_role_id', 0)
        self.full_access_role = r.int('full_access_role_id', 0)

    # ---- helpers ----
    def _has_rules(self, member_id: int) -> bool:
        return member_id in self.bot.data_manager.get('agreed_rules')

    def _has_engage(self, member_id: int) -> bool:
        return member_id in self.bot.data_manager.get('agreed_engagement')

    def _has_nick_claim(self, member_id: int) -> bool:
        """Claimed = agreed_nickname; pending/verified tracked separately."""
        return member_id in self.bot.data_manager.get('agreed_nickname')

    async def maybe_apply_full_access(self, member: discord.Member):
        """Grant when Rules+RoE+Nickname *claimed*; revoke when any missing."""
        guild = member.guild
        role = guild.get_role(self.full_access_role) if self.full_access_role else None
        if not role:
            return

        has_all = self._has_rules(member.id) and self._has_engage(member.id) and self._has_nick_claim(member.id)
        try:
            if has_all and role not in member.roles:
                await member.add_roles(role, reason="All agreements completed (nickname may be pending)")
            elif not has_all and role in member.roles:
                await member.remove_roles(role, reason="Agreements incomplete or nickname unclaimed")
        except discord.Forbidden:
            pass
        except Exception:
            pass

        # Best-effort: refresh user card
        cards = self.bot.get_cog('UserCardsCog')
        if cards:
            try:
                await cards.refresh_card(member)
            except Exception:
                pass

    async def _get_member(self, guild: discord.Guild, user_id: int):
        if not guild:
            return None
        m = guild.get_member(user_id)
        if m is None:
            try:
                m = await guild.fetch_member(user_id)
            except Exception:
                return None
        return m

    async def _user_has_any_accept(self, guild: discord.Guild, channel_id: int, message_id: int, user_id: int) -> bool:
        """Return True if the user still has at least one 'accept' reaction on the message."""
        try:
            ch = guild.get_channel(channel_id)
            if not ch:
                return False
            msg = await ch.fetch_message(message_id)
            for rxn in msg.reactions:
                if is_accept(rxn.emoji):
                    async for u in rxn.users(limit=None):
                        if u.id == user_id:
                            return True
            return False
        except Exception:
            return False

    # ---- commands (hybrid = prefix + slash) ----
    @app_commands.command(name="nick_same", description="Claim that your global display name matches your in-game name (triggers mod review)")
    async def nick_same(self, interaction: discord.Interaction):
        if not interaction.guild or not isinstance(interaction.user, discord.Member):
            return await interaction.response.send_message("Use this in a server.", ephemeral=True)

        member: discord.Member = interaction.user

        nn = self.bot.get_cog('NickNudgeCog')
        if nn and hasattr(nn, 'ensure_pending_and_maybe_open'):
            try:
                await nn.ensure_pending_and_maybe_open(interaction.guild, member, source="nick_same")
            except Exception:
                pass

        await self.maybe_apply_full_access(member)
        await interaction.response.send_message("Thanks — your nickname claim was sent for moderator review.", ephemeral=True)

    # ---- listeners ----
    @commands.Cog.listener()
    async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent):
        if not payload.guild_id or not is_accept(payload.emoji):
            return
        guild = self.bot.get_guild(payload.guild_id)
        member = await self._get_member(guild, payload.user_id)
        if not member or member.bot:
            return

        dm = self.bot.data_manager
        try:
            if payload.message_id == self.rules_msg_id:
                role = guild.get_role(self.rules_role)
                if role:
                    await member.add_roles(role, reason="Agreed to rules")
                if member.id not in dm.get('agreed_rules'):
                    dm.add('agreed_rules', int(member.id))

            elif payload.message_id == self.engage_msg_id:
                role = guild.get_role(self.engage_role)
                if role:
                    await member.add_roles(role, reason="Agreed to engagement")
                if member.id not in dm.get('agreed_engagement'):
                    dm.add('agreed_engagement', int(member.id))

            elif payload.message_id == self.nick_msg_id:
                # --- STALE-STATE CLEANUP ---
                # If no pending review exists for this user in this guild, make sure 'nick_claim_pending'
                # is cleared so the atomic method can transition and open a new review.
                has_pending_review = any(
                    r.get('guild_id') == guild.id and r.get('user_id') == member.id and r.get('status') == 'pending'
                    for r in dm.get('nick_reviews')
                )
                if not has_pending_review:
                    dm.remove('nick_claim_pending', lambda x: x == member.id)

                # Atomic claim -> ONE review only
                nn = self.bot.get_cog('NickNudgeCog')
                if nn and hasattr(nn, 'ensure_pending_and_maybe_open'):
                    try:
                        await nn.ensure_pending_and_maybe_open(guild, member, source="claim")
                    except Exception:
                        pass
                else:
                    return
            else:
                return
        except Exception:
            pass

        await self.maybe_apply_full_access(member)

    @commands.Cog.listener()
    async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent):
        if not payload.guild_id or not is_accept(payload.emoji):
            return
        guild = self.bot.get_guild(payload.guild_id)
        member = await self._get_member(guild, payload.user_id)
        if not member or member.bot:
            return

        dm = self.bot.data_manager
        try:
            if payload.message_id == self.rules_msg_id:
                dm.remove('agreed_rules', lambda x: x == member.id)
                role = guild.get_role(self.rules_role)
                if role:
                    await member.remove_roles(role, reason="Rules un-ticked")

            elif payload.message_id == self.engage_msg_id:
                dm.remove('agreed_engagement', lambda x: x == member.id)
                role = guild.get_role(self.engage_role)
                if role:
                    await member.remove_roles(role, reason="Engagement un-ticked")

            elif payload.message_id == self.nick_msg_id:
                # Clear only if the user has NO accept reactions left on the message
                still_has_accept = await self._user_has_any_accept(
                    guild, payload.channel_id, payload.message_id, member.id
                )
                if not still_has_accept:
                    dm.remove('agreed_nickname', lambda x: x == member.id)
                    dm.remove('nick_claim_pending', lambda x: x == member.id)
                    dm.remove('nick_verified', lambda x: x == member.id)
            else:
                return
        except Exception:
            pass

        await self.maybe_apply_full_access(member)


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