# retoor <retoor@molodetz.nl>

import asyncio
import logging
import random
import re
import time
from typing import Any, Optional

from devplacepy.services.bot.config import persona_article_score, pick_category

logger = logging.getLogger(__name__)

_MENTION_RE = re.compile(r"@([A-Za-z0-9_-]+)")


class BotHelpersMixin:
    def _identity(self) -> str:
        return f"{self.state.username or '?'}/{self.state.persona}"

    def _cost_tag(self) -> str:
        return f"${self.llm.total_cost:.4f} / {self.llm.total_calls} calls"

    def _notify(self, line: str) -> None:
        if self.on_event:
            try:
                self.on_event(line)
            except Exception as e:
                logger.debug("on_event failed: %s", e)

    @staticmethod
    def _thread_id(url: str) -> str:
        m = re.search(r"/(?:posts|gists|news|projects)/([^/?#]+)", url)
        return m.group(1) if m else ""

    def _sanitize_mentions(self, text: str) -> str:
        own = (self.state.username or "").lower()
        seen: set[str] = set()

        def keep(match: "re.Match[str]") -> str:
            handle = match.group(1)
            lowered = handle.lower()
            if lowered == own or lowered in seen:
                return ""
            seen.add(lowered)
            return match.group(0)

        cleaned = _MENTION_RE.sub(keep, text)
        return re.sub(r"\s{2,}", " ", cleaned).strip()

    def _sync_cost(self) -> None:
        self.state.total_cost = self.llm.total_cost
        self.state.total_calls = self.llm.total_calls
        self.state.total_in_tokens = self.llm.total_in_tokens
        self.state.total_out_tokens = self.llm.total_out_tokens

    def _save(self) -> None:
        self._sync_cost()
        self.state.save(self.state_path)

    def _log(self, msg: str) -> None:
        self.state.log.append(f"[{time.strftime('%H:%M:%S')}] {msg}")
        if len(self.state.log) > 500:
            self.state.log = self.state.log[-250:]
        logger.info("[%s] %s", self._identity(), msg)

    def _action(self, tag: str, detail: str) -> None:
        line = f"{tag}: {detail}"
        self.state.log.append(f"[{time.strftime('%H:%M:%S')}] {line}")
        if len(self.state.log) > 500:
            self.state.log = self.state.log[-250:]
        logger.info("[%s] %s (%s)", self._identity(), line, self._cost_tag())
        self._notify(f"[{self._identity()}] {line}")
        self.b.note(action=line)
        asyncio.create_task(self.b.capture(tag.lower(), force=True))

    def _bind_monitor(self) -> None:
        self.b.bind_monitor(
            self._slot,
            username=self.state.username,
            persona=self.state.persona,
        )

    async def _generate(self, fn: Any, *args: Any) -> Any:
        try:
            return await asyncio.to_thread(fn, *args)
        except Exception as e:
            logger.warning(
                "LLM generation failed (%s): %s", getattr(fn, "__name__", fn), e
            )
            return None

    def _cost_summary(self, label: str) -> None:
        self._notify(
            f"[{self._identity()}] cost ({label}): ${self.llm.total_cost:.6f} "
            f"over {self.llm.total_calls} calls (in={self.llm.total_in_tokens} out={self.llm.total_out_tokens})"
        )

    def _session_summary(self, mood: str, actions: int) -> None:
        s = self.state
        sess_cost = self.llm.total_cost - self._session_start_cost
        sess_calls = self.llm.total_calls - self._session_start_calls
        self._notify(
            f"[{self._identity()}] session done mood={mood} actions={actions} "
            f"posts={s.created_posts} comments={s.comments_posted} votes={s.votes_cast} "
            f"session=${sess_cost:.4f}/{sess_calls} lifetime=${self.llm.total_cost:.4f}"
        )

    def _session_banner(self, mood: str, session_len: int) -> None:
        self._notify(
            f"[{self._identity()}] session start mood={mood} target~{session_len} actions"
        )

    def _pick_article(self, articles: list[dict]) -> Optional[dict]:
        if not articles:
            return None
        ranked = sorted(
            articles,
            key=lambda a: persona_article_score(a, self.state.persona)
            + random.random(),
            reverse=True,
        )
        top = ranked[: max(3, len(ranked) // 3)]
        return random.choice(top)

    async def _refill_cache_async(self, kind: str) -> None:
        try:
            if kind == "post":
                articles = self.news.fetch()
                a = self._pick_article(articles)
                if a:
                    desc = a.get("description", "")[:500]
                    cat = pick_category(self.state.persona)
                    post_title = await asyncio.to_thread(
                        self.llm.generate_post_title,
                        a["title"],
                        self.state.persona,
                        cat,
                    )
                    content = await asyncio.to_thread(
                        self.llm.generate_post,
                        a["title"],
                        desc,
                        self.state.persona,
                        cat,
                        self.state.recent_post_titles,
                    )
                    self._post_cache.append(
                        (a["title"], post_title or "", desc, content, cat)
                    )
            elif kind == "project":
                t = await asyncio.to_thread(
                    self.llm.generate_project_title, self.state.persona
                )
                desc = await asyncio.to_thread(
                    self.llm.generate_project_desc, t, self.state.persona
                )
                self._project_cache.append((t, desc))
            elif kind == "issue":
                topic = random.choice(["UI glitch", "Broken link", "Performance issue"])
                issue = await asyncio.to_thread(self.llm.generate_issue, topic)
                self._issue_cache.append(issue)
        except Exception as e:
            logger.warning("Cache refill (%s) failed: %s", kind, e)

    async def _warm_cache(self) -> None:
        self._log("Warming content cache (background)...")
        self._post_cache = []
        self._project_cache = []
        self._issue_cache = []
        self.news.fetch()
        asyncio.create_task(self._refill_cache_async("post"))
        asyncio.create_task(self._refill_cache_async("project"))
        asyncio.create_task(self._refill_cache_async("issue"))
