import random
from typing import Dict, List

from playwright.sync_api import TimeoutError as PlaywrightTimeoutError, expect

from dulizhan.test_case.Resource.UI.base_page import BasePage


class BlogPage(BasePage):
    """
    Joyhub Blog/内容页页面对象。

    说明：
    - 页面侦察结果未发现明确的 Blog 导航入口；
    - 已发现真实导航候选包含 Discover、Following；
    - 因此进入 blog/内容列表时优先尝试 Blog，失败后使用 Discover 作为内容入口兜底。
    """

    HOME_URL = "https://joyhub-website-frontend-test.best-envision.com/"
    EXPECTED_HOME_TITLE = "Joyhub | Explore Sexual Health, Wellness, and Connection"

    NAV_DISCOVER_LINK = "Discover"

    def open_home_page(self) -> None:
        self.goto(self.HOME_URL)
        self.accept_cookie_if_present()
        expect(self.page).to_have_title(self.EXPECTED_HOME_TITLE)

    def enter_blog_page(self) -> str:
        """
        进入 blog/内容页。
        优先级：
        1. Blog 链接：如果页面后续版本新增 Blog 导航，可直接命中；
        2. Discover 链接：侦察结果中存在，作为当前可用内容入口；
        3. /blog 直达：仅作为最后兜底，并在无法点击入口时使用。
        """
        before_url = self.page.url

        blog_locators = [
            self.page.get_by_role("link", name="Blog"),
            self.page.locator("a", has_text="Blog"),
            self.page.get_by_role("link", name=self.NAV_DISCOVER_LINK),
            self.page.locator("a", has_text=self.NAV_DISCOVER_LINK),
        ]

        for locator in blog_locators:
            try:
                if locator.count() > 0 and locator.first.is_visible():
                    locator.first.click()
                    self.wait_for_page_ready()
                    return self.page.url
            except Exception:
                continue

        fallback_blog_url = self.HOME_URL.rstrip("/") + "/blog"
        self.goto(fallback_blog_url)

        assert self.page.url != before_url or "/blog" in self.page.url.lower(), (
            "未能进入 blog/内容页：页面未发现 Blog 入口，Discover 入口也不可点击。"
        )
        return self.page.url

    def _get_candidate_blog_links(self) -> List[Dict[str, str]]:
        """
        从渲染后的页面中提取可点击的 blog/content 候选链接。

        不硬编码未知 selector，优先根据 href 语义筛选：
        blog/article/post/discover/detail/content。
        若无命中，则退化为 main 区域内可见链接，排除导航类链接。
        """
        return self.page.evaluate(
            """
            () => {
                const navTexts = new Set([
                    'Home',
                    'Download the App',
                    'Rewards',
                    'Support',
                    'About Us',
                    'Discover',
                    'Following',
                    'Partnerships',
                    'FAQs',
                    'Login'
                ]);

                const normalize = value => (value || '').replace(/\\s+/g, ' ').trim();

                const links = Array.from(document.querySelectorAll('a[href]'))
                    .filter(a => {
                        const rect = a.getBoundingClientRect();
                        const style = window.getComputedStyle(a);
                        return rect.width > 0
                            && rect.height > 0
                            && style.visibility !== 'hidden'
                            && style.display !== 'none';
                    })
                    .map((a, index) => {
                        const href = a.href || '';
                        const text = normalize(a.innerText || a.textContent || a.getAttribute('aria-label') || '');
                        const imgAlt = normalize(
                            Array.from(a.querySelectorAll('img'))
                                .map(img => img.alt)
                                .filter(Boolean)
                                .join(' ')
                        );

                        return {
                            index,
                            href,
                            text,
                            imgAlt,
                            pathname: (() => {
                                try { return new URL(href).pathname.toLowerCase(); }
                                catch(e) { return ''; }
                            })()
                        };
                    })
                    .filter(item => item.href && !item.href.startsWith('javascript:'));

                const semanticLinks = links.filter(item =>
                    /blog|article|post|discover|detail|content|story/i.test(item.href)
                    && !navTexts.has(item.text)
                );

                if (semanticLinks.length > 0) {
                    return semanticLinks;
                }

                return links.filter(item =>
                    !navTexts.has(item.text)
                    && item.href !== window.location.href
                    && !/#$/.test(item.href)
                );
            }
            """
        )

    def click_random_blog(self) -> Dict[str, str]:
        """
        随机点击一个 blog/content 候选项。
        点击后等待 URL 或页面内容变化。
        """
        self.wait_for_page_ready()
        candidates = self._get_candidate_blog_links()

        assert candidates, (
            "未找到可点击的 blog/content 候选链接。"
            "页面侦察结果缺少明确 blog 卡片 selector，请补充稳定定位，如 data-testid='blog-card'。"
        )

        candidate = random.choice(candidates)
        before_url = self.page.url

        locator = self.page.locator("a[href]").nth(candidate["index"])
        locator.scroll_into_view_if_needed(timeout=5000)

        try:
            with self.page.expect_navigation(wait_until="domcontentloaded", timeout=8000):
                locator.click()
        except PlaywrightTimeoutError:
            locator.click()

        self.wait_for_page_ready()

        after_url = self.page.url
        assert after_url != before_url or self.page.locator("main, article, body").first.is_visible(), (
            f"点击 blog/content 候选项后页面未出现有效跳转或内容区域。候选链接：{candidate['href']}"
        )

        return {
            "clicked_href": candidate.get("href", ""),
            "clicked_text": candidate.get("text") or candidate.get("imgAlt") or "",
            "before_url": before_url,
            "after_url": after_url,
        }

    def assert_blog_content_page_loaded(self) -> None:
        """
        断言已进入 blog/content 内容页。
        因页面缺少稳定详情页 selector，这里采用内容区域可见的稳健断言。
        """
        self.wait_for_page_ready()

        content_locator = self.page.locator("article, main, [role='main'], body").first
        expect(content_locator).to_be_visible(timeout=10000)

        body_text = self.page.locator("body").inner_text(timeout=10000).strip()
        assert len(body_text) > 0, "blog/content 页面 body 内容为空，疑似跳转失败或页面未渲染完成。"

    def capture_blog_content_screenshot(self, screenshot_dir: str, case_key: str) -> str:
        return self.screenshot(
            screenshot_dir=screenshot_dir,
            file_name=f"{case_key}_blog_content.png",
            full_page=True,
        )
