# retoor <retoor@molodetz.nl>

import asyncio
import hashlib
import logging
import random

from devplacepy.services.bot.config import pick_category

logger = logging.getLogger(__name__)


class BotPostingMixin:
    async def _create_gist(self) -> bool:
        b = self.b
        await b.goto(f"{self.base_url}/gists")
        if not await b.click("#create-gist-btn, .gists-fab, .feed-fab"):
            self._log("Create gist: trigger not found")
            return False
        await b._idle(0.8, 1.5)
        if await b.page.locator("#create-gist-modal").count() == 0:
            self._log("Create gist: modal not found")
            return False

        generated = None
        for attempt in range(2):
            candidate = await self._generate(self.llm.generate_gist, self.state.persona)
            if not candidate:
                continue
            cand_title, cand_desc, cand_lang, cand_code = candidate
            if not cand_code.strip():
                continue
            verdict = await self._generate(
                self.llm.gist_quality_check, cand_title, cand_code, cand_lang
            )
            if verdict is None or verdict[0]:
                generated = candidate
                break
            self.state.quality_rejections += 1
            action = "regenerating" if attempt == 0 else "skipping"
            self._log(f"Gist quality reject ({verdict[1]}), {action}")
        if not generated:
            self._log("Create gist: no quality candidate")
            return False
        title, desc, language, code = generated

        title = self.llm.strip_md(title)[:200] or title[:200]
        if not await b.fill("#gist-title", title):
            self._log("Create gist: title field not found")
            return False
        await b._idle(0.2, 0.5)
        await b.fill("#gist-description", desc[:500])
        await b._idle(0.2, 0.5)
        try:
            await b.page.select_option("#gist-language", language)
        except Exception as e:
            logger.debug("gist language select failed: %s", e)
        await b._idle(0.3, 0.7)

        cm = b.page.locator("#create-gist-modal .CodeMirror")
        if await cm.count() == 0:
            self._log("Create gist: code editor not found")
            return False
        try:
            await cm.first.click()
            await b._idle(0.3, 0.6)
            await b.page.evaluate(
                """(code) => {
                    const el = document.querySelector('#create-gist-modal .CodeMirror');
                    if (el && el.CodeMirror) { el.CodeMirror.setValue(code); el.CodeMirror.save(); }
                    const ta = document.querySelector('#gist-source-editor');
                    if (ta) { ta.value = code; ta.dispatchEvent(new Event('input', {bubbles: true})); }
                }""",
                code,
            )
        except Exception as e:
            logger.debug("gist code fill failed: %s", e)
            return False
        await b._idle(0.5, 1.2)

        ok = await b.click(
            "#create-gist-modal button[type='submit'], #create-gist-modal .btn-primary"
        )
        if not ok:
            self._log("Create gist: submit not found")
            return False
        await asyncio.sleep(2)
        url = await b.url()
        if "/gists/" in url:
            if url not in self.state.own_post_urls:
                self.state.own_post_urls.append(url)
            self.state.gists_created += 1
            self._action(
                "GIST",
                f"{title[:40]} [{language}] [{self.state.gists_created}] -> {url[-50:]}",
            )
            return True
        self._log("Create gist: no redirect to gist detail")
        return False

    async def _create_post(self) -> bool:
        b = self.b
        await b.goto(f"{self.base_url}/feed")

        fab_sel = "#create-post-btn, .feed-fab, button.feed-fab, a.feed-fab"
        ok = await b.click(fab_sel)
        if not ok:
            self._log("Create post: FAB button not found")
            return False
        self._log("Create post: FAB clicked, waiting for modal")
        await b._idle(1.0, 2.0)

        modal = await b.page.locator("#create-post-modal").count()
        if modal == 0:
            self._log("Create post: modal #create-post-modal not in DOM")
            return False
        hidden = await b.page.locator("#create-post-modal").get_attribute("class") or ""
        if "hidden" in hidden or await b.page.locator("#create-post-modal").is_hidden():
            self._log("Create post: modal is hidden")
            return False
        self._log("Create post: modal visible")

        article_title = ""
        article_desc = ""
        post_title = ""

        if self._post_cache:
            article_title, post_title, article_desc, content, topic = (
                self._post_cache.pop(0)
            )
            asyncio.create_task(self._refill_cache_async("post"))
        else:
            articles = self.news.fetch()
            topic = pick_category(self.state.persona)
            article = self.registry.reserve_unused(
                articles,
                self.state.username,
                self.llm.clean,
                topic,
                self.state.persona,
            )
            if not article:
                self._log("Create post: no unused articles available")
                return False
            desc = article.get("description", "")[:500]
            post_title = await self._generate(
                self.llm.generate_post_title,
                article["title"],
                self.state.persona,
                topic,
            )
            content = await self._generate(
                self.llm.generate_post,
                article["title"],
                desc,
                self.state.persona,
                topic,
                self.state.recent_post_titles,
            )
            if not content:
                self._log("Create post: generation failed/empty")
                return False
            article_title = article["title"]
            article_desc = desc

        verdict = await self._generate(
            self.llm.quality_check, "post", content, article_desc
        )
        if verdict is not None and not verdict[0]:
            self.state.quality_rejections += 1
            self._log(f"Post quality reject ({verdict[1]}), regenerating")
            content = await self._generate(
                self.llm.generate_post,
                article_title,
                article_desc,
                self.state.persona,
                topic,
                self.state.recent_post_titles,
            )
            if not content:
                self._log("Create post: regeneration failed/empty")
                return False
            verdict = await self._generate(
                self.llm.quality_check, "post", content, article_desc
            )
            if verdict is not None and not verdict[0]:
                self.state.quality_rejections += 1
                self._log(f"Post quality reject ({verdict[1]}), skipping")
                return False

        content_hash = hashlib.sha256(content.encode()).hexdigest()[:16]
        if content_hash in self.state.posted_content_hashes:
            self._log(f"Create post: duplicate content {content_hash}, skipping")
            return False

        clean_title = self.llm.clean(article_title[:200]) if article_title else ""
        if clean_title:
            if not self.registry.reserve(clean_title, self.state.username, topic):
                self._log(
                    f"Create post: article '{clean_title[:50]}...' already covered, skipping"
                )
                return False

        type_title = self.llm.strip_md(post_title)[:120] if post_title else ""
        if not type_title:
            type_title = clean_title

        ts = f"#create-post-modal input[name='topic'][value='{topic}']"
        if await b.page.locator(ts).count() == 0:
            self._log(f"Create post: topic radio '{topic}' not found")
        else:
            await b.click(ts)
            await b._idle(0.2, 0.5)

        if type_title:
            ok = await b.fill("#create-post-modal input[name='title']", type_title)
            if not ok:
                self._log("Create post: title input not found")
            await b._idle(0.2, 0.4)

        content_sel = "#create-post-modal textarea[name='content']"
        if await b.page.locator(content_sel).count() == 0:
            self._log("Create post: content textarea not found")
            return False
        if not await self._type_like_human(content_sel, content[:5000]):
            self._log("Create post: typing content into modal failed")
            return False
        await b._idle(0.5, 1.5)

        submit_sels = [
            "#create-post-modal button[type='submit']",
            "#create-post-modal .btn-primary",
            ".modal-overlay#create-post-modal button[type='submit']",
            "#create-post-modal button:has-text('Post')",
        ]
        ok = False
        for sel in submit_sels:
            if await b.click(sel):
                ok = True
                break
        if not ok:
            self._log("Create post: submit button not found")
            return False

        post_url = await b.url()
        for _ in range(12):
            if "/posts/" in post_url:
                break
            await asyncio.sleep(1)
            post_url = await b.url()
        if "/posts/" not in post_url:
            self._log(f"Create post: submit did not create a post (url={post_url[-45:]})")
            return False
        self.state.created_posts += 1
        self.state.posted_content_hashes.append(content_hash)
        if type_title:
            self.state.recent_post_titles.append(type_title)
            self.state.recent_post_titles = self.state.recent_post_titles[-8:]
        if post_url not in self.state.own_post_urls:
            self.state.own_post_urls.append(post_url)
        if post_url not in self.state.known_posts:
            self.state.known_posts.append(post_url)
        self._action(
            "POST",
            f"{topic} [{self.state.created_posts}]: {type_title[:50] or content[:50]} -> {post_url[-45:]}",
        )
        return True

    async def _click_post_link(self) -> bool:
        b = self.b
        links = b.page.locator("a[href*='/posts/']")
        count = await links.count()
        if count == 0:
            return False

        candidates = []
        for i in range(count):
            h = await links.nth(i).get_attribute("href")
            if h and "/posts/" in h and len(h) > 20:
                candidates.append(i)

        if not candidates:
            return False

        idx = random.choice(candidates)
        href = await links.nth(idx).get_attribute("href")
        try:
            await links.nth(idx).click(timeout=5000)
            await b._idle(1.0, 2.0)
            if href and href not in self.state.known_posts:
                self.state.known_posts.append(href)
            self._action("OPEN", f"post {href[-45:] if href else '?'}")
            return True
        except Exception as e:
            logger.debug("_click_post_link failed: %s", e)
            return False

    async def _click_gist_link(self) -> bool:
        b = self.b
        links = b.page.locator("a[href*='/gists/']")
        count = await links.count()
        if count == 0:
            return False
        candidates = []
        for i in range(count):
            h = await links.nth(i).get_attribute("href")
            if h and "/gists/" in h and "?" not in h and h.rstrip("/") != "/gists":
                candidates.append(i)
        if not candidates:
            return False
        idx = random.choice(candidates)
        href = await links.nth(idx).get_attribute("href")
        try:
            await links.nth(idx).click(timeout=5000)
            await b._idle(1.0, 2.0)
            self._action("OPEN", f"gist {href[-45:] if href else '?'}")
            return True
        except Exception as e:
            logger.debug("_click_gist_link failed: %s", e)
            return False

    async def _open_content(self, kind: str) -> bool:
        b = self.b
        base = "/news/" if kind == "news" else f"/{kind}s/"
        links = b.page.locator(f"a[href*='{base}']")
        count = await links.count()
        if count == 0:
            return False
        candidates = []
        for i in range(count):
            h = await links.nth(i).get_attribute("href")
            if h and base in h and "?" not in h and h.rstrip("/") != base.rstrip("/"):
                candidates.append(i)
        if not candidates:
            return False
        idx = random.choice(candidates)
        href = await links.nth(idx).get_attribute("href")
        try:
            await links.nth(idx).click(timeout=5000)
            await b._idle(1.0, 2.0)
            self._action("OPEN", f"{kind} {href[-45:] if href else '?'}")
            return True
        except Exception as e:
            logger.debug("_open_content(%s) failed: %s", kind, e)
            return False
