# modules/status/status_rotator.py
import random
import asyncio
import time
from datetime import datetime, timezone, timedelta

import discord
from discord.ext import commands, tasks

from modules.common.settings import cfg

# ============== Tunables / lists you can expand freely ==============

# Long list of expressive unicode emojis (safe for all clients)
EMOTES = [
    "❤️","🧡","💛","💚","💙","💜","🤎","🖤","🤍","✨","🌟","⭐","🎉","🎊","🔥","💫","⚡","🌈",
    "😄","😁","😆","😊","🙂","😉","😎","🤩","🥳","🤗","🙌","👏","👍","🤝","🫶","🙏","🫡","🤘","💪",
    "👀","🤔","🧐","😼","😹","😏","😌","😇","😴","🤖","👾","🧠","🫠",
    "🏜️","🌵","🐪","🐛","🪱","🧂","🧪","🗡️","⚔️","🛡️","🚁","🛩️","🚀","🧭","🌪️"
]

DUNE_PHRASES = [
    "Arrakis. Dune. Desert Planet.",
    "Shai-Hulud stirs beneath the sands.",
    "The spice must flow.",
    "Bene Gesserit whispers in the dark.",
    "Kwisatz Haderach foretold.",
    "House Atreides rises.",
    "House Harkonnen plots.",
    "Fremen walk without rhythm.",
    "Crysknife unsheathed.",
    "Sietch Tabr stands strong.",
    "CHOAM counts its profits.",
    "The Spacing Guild navigates the void.",
    "Water is life.",
    "Fear is the mind-killer.",
    "Gom Jabbar at the throat.",
    "Stillsuits conserve every drop.",
    "Ornithopters in the storm.",
    "Sardaukar march.",
    "Prescience veils the future.",
    "Fedāykin watchful in the dunes.",
    "Shields hum under the sun.",
    "Kanly declared.",
    "Desert winds whisper secrets.",
    "Muad’Dib walks the golden path.",
    "Sandtrout seal the desert’s fate.",
    "Made with ❤️ in 🇳🇴",
    "DD Reset: Tuesday 03:00 UTC"
]

FUN_FACTS = [
    "Frank Herbert conceived Dune after reporting on sand dune stabilization in the Oregon coast.",
    "‘Muad’Dib’ is a small desert mouse whose footprints taught Paul the sandwalk.",
    "Melange extends life, heightens awareness, and enables prescient navigation.",
    "Fremen ‘walking without rhythm’ avoids attracting sandworms.",
    "The crysknife is made from a sandworm’s tooth and must never be sheathed unblooded.",
    "Spacing Guild Navigators require spice to safely fold space.",
    "The Litany Against Fear is recited to focus and master one’s emotions.",
    "Bene Gesserit use prana-bindu training to control every muscle fiber.",
    "Sietch means a Fremen community cave complex.",
    "Stillsuits can reclaim over 90% of the body’s moisture.",
    "The Imperial throne balances the Landsraad, CHOAM, and the Guild.",
    "Kanly refers to a formal vendetta between noble houses.",
    "Arrakis once had open water—long before the events of Dune.",
    "‘Shai-Hulud’ is the Fremen name for the grand sandworm.",
    "A lasgun-shield interaction can cause a subatomic chain reaction.",
    "Mentats are human computers trained to replace forbidden thinking machines.",
    "Duncan Idaho appears across eras via ghola rebirths.",
    "The Water of Life is a deadly spice exhalation—surviving it transforms the Reverend Mother.",
    "Fedaykin are the Fremen elite commandos sworn to their leader.",
    "Sandtrout are juvenile forms of the great sandworms.",
    "The Butlerian Jihad outlawed thinking machines, reshaping human society.",
    "Caladan is the Atreides ocean world before their move to Arrakis.",
    "The Harkonnen homeworld is Giedi Prime, an industrialized, harsh planet.",
    "‘He who controls the spice controls the universe.’",
    "The Weirding Way is a Bene Gesserit martial art emphasizing speed and economy.",
    "Sardaukar troops are trained from birth on the prison planet Salusa Secundus.",
    "Ornithopters mimic bird flight to navigate harsh desert storms.",
    "The Fremen call offworlders ‘water-fat’ as an insult.",
    "Spice blows are natural melange eruptions from the deep desert.",
    "A ~30 year old male weighing ~75kg will consist of around 45L of water.",
    "A simple interactive DD map can be found at https://dune.gaming.tools/deep-desert."
]


# ============== Cog implementation ==============

class StatusRotatorCog(commands.Cog):
    """
    Cycles bot presence every N minutes with randomized status entries:
      - Member count (excluding bots)
      - Pirates count
      - Encounters count
      - Random shout-out: {emoji} {display_name} (members with SHAI_FULL_ACCESS_ROLE_ID)
      - Count of fully initiated members (role members)
      - Random Dune phrase
      - Random Dune fun fact (#n -> fact)
      - Top threat pirate
      - Latest report (pending/most recent)
      - Encounters in last 24h
      - Uptime
      - Current running version
    """

    def __init__(self, bot: commands.Bot):
        self.bot = bot
        r = cfg(bot)
        self.home_gid = r.int('home_guild_id', 0)
        self.full_access_role_id = r.int('full_access_role_id', 0)  # SHAI_FULL_ACCESS_ROLE_ID
        self.interval_min = r.int('status_interval_min', 5)         # SHAI_STATUS_INTERVAL_MIN (optional)
        self.max_len = 120  # clip safety for PC clients (keeps presence tidy)

        # Build generator list (enable/disable here if needed)
        self._generators = [
            self._gen_members_count,
            self._gen_pirates_count,
            self._gen_encounters_count,
            self._gen_random_shoutout,
            self._gen_initiated_count,
            self._gen_random_phrase,
            self._gen_random_fun_fact,
            self._gen_top_threat,
            self._gen_latest_report,
            self._gen_encounters_last_24h,
            self._gen_uptime,
            self._gen_running_version,
        ]
        self._queue = []

    # ---- lifecycle ----

    @commands.Cog.listener()
    async def on_ready(self):
        # ensure loop interval reflects config
        try:
            if self.rotate_status.is_running():
                self.rotate_status.change_interval(minutes=max(1, self.interval_min))
            else:
                self.rotate_status.change_interval(minutes=max(1, self.interval_min))
                self.rotate_status.start()
        except Exception as e:
            print("[status] failed to start/change loop:", repr(e))

    def cog_unload(self):
        try:
            self.rotate_status.cancel()
        except Exception:
            pass

    # ---- the loop ----

    @tasks.loop(minutes=5)
    async def rotate_status(self):
        await self.bot.wait_until_ready()
        guild = self._resolve_guild()
        if not guild:
            return

        text = await self._next_status_text(guild)
        if not text:
            return

        # Enforce Custom Status; if it fails for any reason, fall back to Watching.
        try:
            await self.bot.change_presence(activity=discord.CustomActivity(name=text))
        except Exception as e:
            print("[status] custom presence failed, falling back to Watching:", repr(e))
            try:
                await self.bot.change_presence(
                    activity=discord.Activity(type=discord.ActivityType.watching, name=text)
                )
            except Exception as e2:
                print("[status] change_presence failed:", repr(e2))

    # ---- helpers ----

    def _resolve_guild(self) -> discord.Guild | None:
        if self.home_gid:
            g = self.bot.get_guild(self.home_gid)
            if g:
                return g
        return self.bot.guilds[0] if self.bot.guilds else None

    async def _next_status_text(self, guild: discord.Guild) -> str | None:
        # Try up to len(generators) times to get a non-empty status
        for _ in range(len(self._generators)):
            if not self._queue:
                self._queue = random.sample(self._generators, k=len(self._generators))
            gen = self._queue.pop(0)
            try:
                s = await gen(guild)
                s = self._clip(s)
                if s:
                    return s
            except Exception as e:
                print(f"[status] generator {gen.__name__} failed:", repr(e))
                continue
        return None

    def _clip(self, s: str | None) -> str | None:
        if not s:
            return None
        s = s.strip()
        return (s[: self.max_len - 1] + "…") if len(s) > self.max_len else s

    @staticmethod
    def _fmt_duration(seconds: float) -> str:
        seconds = int(max(0, seconds))
        d, rem = divmod(seconds, 86400)
        h, rem = divmod(rem, 3600)
        m, _ = divmod(rem, 60)
        if d > 0:
            return f"{d}d {h}h {m}m"
        if h > 0:
            return f"{h}h {m}m"
        return f"{m}m"

    # ---- individual generators (originals) ----

    async def _gen_members_count(self, guild: discord.Guild) -> str:
        count = sum(1 for m in guild.members if not m.bot)
        return f"{count} server members"

    async def _gen_pirates_count(self, guild: discord.Guild) -> str:
        dm = getattr(self.bot, "data_manager", None)
        n = len(dm.get('pirates')) if dm else 0
        return f"{n} pirates reported"

    async def _gen_encounters_count(self, guild: discord.Guild) -> str:
        dm = getattr(self.bot, "data_manager", None)
        n = len(dm.get('encounters')) if dm else 0
        return f"{n} reported encounters"

    async def _gen_random_shoutout(self, guild: discord.Guild) -> str | None:
        if not self.full_access_role_id:
            return None
        role = guild.get_role(self.full_access_role_id)
        if not role:
            return None
        candidates = [m for m in role.members if not m.bot]
        if not candidates:
            return None
        member = random.choice(candidates)
        em = random.choice(EMOTES)
        name = member.display_name
        return f"{em} {name}"

    async def _gen_initiated_count(self, guild: discord.Guild) -> str | None:
        if not self.full_access_role_id:
            return None
        role = guild.get_role(self.full_access_role_id)
        if not role:
            return None
        n = sum(1 for m in role.members if not m.bot)
        return f"{n} fully initiated members"

    async def _gen_random_phrase(self, guild: discord.Guild) -> str:
        return random.choice(DUNE_PHRASES)

    async def _gen_random_fun_fact(self, guild: discord.Guild) -> str:
        idx = random.randrange(len(FUN_FACTS))  # 0-based
        num = idx + 1
        fact = FUN_FACTS[idx]
        return f"Fun fact #{num} ->\n{fact}"

    async def _gen_top_threat(self, guild: discord.Guild) -> str | None:
        dm = getattr(self.bot, "data_manager", None)
        pirates = dm.get('pirates') if dm else []
        if not pirates:
            return None
        # sort by threat_level desc, then encounter_count desc, then newest added
        pirates_sorted = sorted(
            pirates,
            key=lambda p: (
                int(p.get('threat_level', 0)),
                int(p.get('encounter_count', 0)),
                float(p.get('added_ts', 0.0))
            ),
            reverse=True
        )
        top = pirates_sorted[0]
        tl = int(top.get('threat_level', 0))
        name = top.get('character_name') or top.get('account_name') or "Unknown"
        return f"Top threat: {name} ({tl}%)"

    async def _gen_latest_report(self, guild: discord.Guild) -> str | None:
        dm = getattr(self.bot, "data_manager", None)
        reports = dm.get('reports') if dm else []
        if not reports:
            return None
        latest = max(reports, key=lambda r: float(r.get('ts', 0.0)))
        char = latest.get('character_name') or "Unknown"
        acct = latest.get('account_name') or ""
        # keep brief
        return f"Latest report: {char}" + (f" ({acct})" if acct else "")

    async def _gen_encounters_last_24h(self, guild: discord.Guild) -> str | None:
        dm = getattr(self.bot, "data_manager", None)
        encs = dm.get('encounters') if dm else []
        if not encs:
            return "0 encounters last 24h"
        since = time.time() - 86400
        n = sum(1 for e in encs if float(e.get('timestamp', 0.0)) >= since)
        return f"{n} encounters last 24h"

    async def _gen_uptime(self, guild: discord.Guild) -> str | None:
        dm = getattr(self.bot, "data_manager", None)
        st = (dm.get('boot_state') or [{}])[-1] if (dm and dm.get('boot_state')) else {}
        boot_ts = float(st.get('last_boot_ts', 0.0))
        if boot_ts <= 0:
            return None
        dur = self._fmt_duration(time.time() - boot_ts)
        return f"Uptime {dur}"

    async def _gen_running_version(self, guild: discord.Guild) -> str | None:
        dm = getattr(self.bot, "data_manager", None)
        st = (dm.get('boot_state') or [{}])[-1] if (dm and dm.get('boot_state')) else {}
        ver = st.get('last_version')
        if not ver:
            return None
        # Occasionally include version (kept as its own generator for randomness)
        return f"Running v{ver}"

# ============== setup() ==============

async def setup(bot: commands.Bot):
    cog = StatusRotatorCog(bot)
    await bot.add_cog(cog)
    print("[status] StatusRotatorCog loaded; rotating presence every",
          max(1, cfg(bot).int('status_interval_min', 5)), "minute(s).")