from __future__ import annotations

import os
import re

from playwright.sync_api import Frame, Page, expect

from e2e.pages.base_page import BasePage
from e2e.utils.bug_reporter import BugReporter


ENTERPRISE_CONSOLE_URL = "https://test-merchant-lianlianyun.lianlianwz.com/productConsole?productCode=P20250421171352724060"


class TenantAuthPage(BasePage):
    def __init__(self, page: Page, tenant_home_url: str, bug_reporter: BugReporter | None = None):
        super().__init__(page)
        self.tenant_home_url = tenant_home_url
        self.bug_reporter = bug_reporter

    def login_main_account(self, phone: str, password: str) -> None:
        setattr(self.page, "_e2e_current_player_account", "")
        if self.can_reuse_login(phone):
            if self.bug_reporter:
                self.bug_reporter.clear()
            return

        self.logout_current_account()
        if self.bug_reporter:
            self.bug_reporter.clear()
        self.goto_login_page()
        phone_input = self.page.locator("#password_login_phone")
        self.fill_and_verify(phone_input, phone)

        password_input = self.page.locator("#password_login_password")
        self.fill_and_verify(password_input, password)
        self.page.get_by_text("登 录", exact=True).click()
        self.page.wait_for_load_state("domcontentloaded")
        self.page.wait_for_timeout(2000)
        self.select_main_account_if_present(phone)
        self.ensure_main_account_login_completed(phone, password)
        self.remember_current_login(phone)

    def ensure_main_account_login_completed(self, phone: str, password: str) -> None:
        for _ in range(2):
            try:
                self.goto(ENTERPRISE_CONSOLE_URL)
                self.wait_for_enterprise_console_shell()
                return
            except Exception:
                body_text = self.page.locator("body").inner_text(timeout=5000)
                if "#password_login_phone" not in body_text and "账号登录" not in body_text and "登 录" not in body_text:
                    break
                phone_input = self.page.locator("#password_login_phone")
                password_input = self.page.locator("#password_login_password")
                if phone_input.count() == 0 or password_input.count() == 0:
                    break
                self.fill_and_verify(phone_input, phone)
                self.fill_and_verify(password_input, password)
                self.page.get_by_text("登 录", exact=True).click()
                self.page.wait_for_load_state("domcontentloaded")
                self.page.wait_for_timeout(2000)
                self.select_main_account_if_present(phone)
        body_text = self.page.locator("body").inner_text(timeout=5000)
        raise AssertionError(f"登录账号 {phone} 后未进入企业控制台，当前页面文本：{body_text[:1000]}")

    def can_reuse_login(self, phone: str) -> bool:
        if os.getenv("E2E_REUSE_TENANT_LOGIN", "").lower() not in {"1", "true", "yes"}:
            return False
        if getattr(self.page, "_e2e_current_main_account", "") != phone:
            return False
        try:
            self.goto(ENTERPRISE_CONSOLE_URL)
            self.wait_for_enterprise_console_shell()
            return True
        except Exception:
            return False

    def remember_current_login(self, phone: str) -> None:
        setattr(self.page, "_e2e_current_main_account", phone)

    def forget_current_login(self) -> None:
        setattr(self.page, "_e2e_current_main_account", "")

    def goto_login_page(self) -> None:
        login_url = self.tenant_home_url.replace("/home", "/login")
        last_error: Exception | None = None
        urls = [login_url, f"{login_url}?tab=password", login_url]
        for url in urls:
            try:
                self.goto(url)
                if self.login_context_ready():
                    return
                if self.wait_for_password_login_form(timeout=5000):
                    return
                self.click_account_login_tab_if_present()
                if self.wait_for_password_login_form(timeout=8000):
                    return
                self.page.wait_for_selector("#password_login_phone", state="visible", timeout=3000)
                self.page.wait_for_selector("#password_login_password", state="visible", timeout=3000)
                return
            except Exception as exc:
                last_error = exc
                self.page.wait_for_timeout(1000)
        body_text = self.page.locator("body").inner_text(timeout=5000)
        raise AssertionError(f"租户登录页未稳定打开：{login_url}，当前页面文本：{body_text[:1000]}") from last_error

    def wait_for_password_login_form(self, timeout: int = 10000) -> bool:
        try:
            self.page.wait_for_selector("#password_login_phone", state="visible", timeout=timeout)
            self.page.wait_for_selector("#password_login_password", state="visible", timeout=timeout)
            return True
        except Exception:
            return False

    def click_account_login_tab_if_present(self) -> None:
        self.page.evaluate(
            """() => {
                const nodes = Array.from(document.querySelectorAll('taro-view-core, div, span, button'));
                const target = nodes.find(el => {
                    const text = (el.innerText || el.textContent || '').trim();
                    const r = el.getBoundingClientRect();
                    return r.width > 0 && r.height > 0 && ['账号登录', '密码登录'].includes(text);
                });
                if (target) target.click();
            }"""
        )
        self.page.wait_for_timeout(800)

    def login_context_ready(self) -> bool:
        try:
            body_text = self.page.locator("body").inner_text(timeout=3000)
        except Exception:
            return False
        return "进入后台" in body_text and "主账号" in body_text or "选择账号" in body_text

    def clear_login_state(self) -> None:
        self.forget_current_login()
        self.page.context.clear_cookies()
        for url in [self.tenant_home_url, ENTERPRISE_CONSOLE_URL]:
            try:
                self.goto(url)
                self.page.evaluate("() => { localStorage.clear(); sessionStorage.clear(); }")
            except Exception:
                continue

    def logout_current_account(self) -> None:
        self.forget_current_login()
        try:
            self.goto(self.tenant_home_url)
            self.page.wait_for_load_state("domcontentloaded")
            self.page.wait_for_timeout(1500)
            if self.click_logout_from_user_menu():
                return
        except Exception:
            pass
        self.clear_login_state()

    def click_logout_from_user_menu(self) -> bool:
        body_text = self.page.locator("body").inner_text(timeout=3000)
        if "退出登录" in body_text:
            if self.click_visible_text_by_js("退出登录"):
                self.confirm_logout_if_present()
                return True

        opened = self.page.evaluate(
            """() => {
                const nodes = Array.from(document.querySelectorAll('taro-view-core, div, span'));
                const target = nodes
                  .map(el => ({ el, text: el.innerText || el.textContent || '', r: el.getBoundingClientRect() }))
                  .filter(item => item.text.includes('主账号') && item.r.y < 120 && item.r.width > 60 && item.r.height > 16)
                  .sort((a, b) => (a.r.width * a.r.height) - (b.r.width * b.r.height))[0];
                if (!target) return false;
                target.el.click();
                return true;
            }"""
        )
        if not opened:
            return False
        self.page.wait_for_timeout(800)
        if not self.click_visible_text_by_js("退出登录"):
            return False
        self.confirm_logout_if_present()
        return True

    def click_visible_text_by_js(self, text: str) -> bool:
        clicked = self.page.evaluate(
            """text => {
                const nodes = Array.from(document.querySelectorAll('taro-view-core, div, span, button'));
                const target = nodes.find(el => {
                    const value = (el.innerText || el.textContent || '').trim();
                    const r = el.getBoundingClientRect();
                    return value === text && r.width > 0 && r.height > 0;
                });
                if (!target) return false;
                target.click();
                return true;
            }""",
            text,
        )
        self.page.wait_for_timeout(1000)
        return bool(clicked)

    def confirm_logout_if_present(self) -> None:
        for text in ["确 定", "确定", "确认", "退 出", "退出"]:
            try:
                locator = self.page.locator(".ant-modal button").filter(has_text=text).first
                if locator.count():
                    locator.click(timeout=1000)
                    self.page.wait_for_timeout(1000)
                    return
            except Exception:
                continue

    def select_main_account_if_present(self, phone: str) -> None:
        body_text = self.page.locator("body").inner_text(timeout=5000)
        if "/account/select" not in self.page.url and "选择账号" not in body_text:
            return

        box = self.page.evaluate(
            """() => {
                const candidates = Array.from(document.querySelectorAll('taro-view-core, div'))
                  .map(el => ({ text: el.innerText || el.textContent || '', r: el.getBoundingClientRect() }))
                  .filter(item => item.text.includes('主账号') && !item.text.includes('子账号'))
                  .filter(item => item.r.width > 300 && item.r.width < 800 && item.r.height > 50 && item.r.height < 140)
                  .sort((a, b) => (a.r.width * a.r.height) - (b.r.width * b.r.height));
                const target = candidates[0];
                if (!target) return null;
                return { x: target.r.x, y: target.r.y, width: target.r.width, height: target.r.height };
            }"""
        )
        if not box:
            raise AssertionError(f"登录账号 {phone} 未找到主账号卡片，当前页面文本：{body_text[:800]}")
        self.page.mouse.click(box["x"] + box["width"] - 30, box["y"] + box["height"] / 2)
        self.page.wait_for_load_state("domcontentloaded")
        self.page.wait_for_timeout(2000)

        selected_text = self.page.locator("body").inner_text(timeout=5000)
        if "主账号" not in selected_text:
            raise AssertionError(f"登录账号 {phone} 未进入主账号上下文，当前页面文本：{selected_text[:800]}")

    def open_enterprise_auth_menu(self, leaf_menu: str) -> None:
        expected_frame = "/secret" if leaf_menu == "授权密钥" else "/myAuth" if leaf_menu == "我的授权" else ""
        last_urls: list[str] = []
        for _ in range(4):
            self.goto(ENTERPRISE_CONSOLE_URL)
            self.wait_for_enterprise_console_shell()
            self.click_sidebar_text("企业", right_edge=True)
            self.click_sidebar_text("授权管理", right_edge=True)
            self.click_sidebar_text(leaf_menu)
            self.page.wait_for_timeout(2500)
            last_urls = [frame.url for frame in self.page.frames]
            if not expected_frame or any(expected_frame in url for url in last_urls):
                return
        raise AssertionError(f"点击授权菜单 {leaf_menu} 后未进入目标 iframe {expected_frame}，当前 frames：{last_urls}")

    def wait_for_enterprise_console_shell(self) -> None:
        for _ in range(6):
            try:
                self.page.wait_for_load_state("domcontentloaded")
                body_text = self.page.locator("body").inner_text(timeout=3000)
                if "账号登录" in body_text and "登录/注册" in body_text:
                    raise AssertionError(f"仍停留在登录页，未进入企业控制台：{body_text[:500]}")
                if body_text.strip() and ("企业" in body_text or "商家管理" in body_text or "授权管理" in body_text):
                    return
            except Exception:
                pass
            self.page.wait_for_timeout(1000)
        self.page.reload(wait_until="domcontentloaded")
        self.page.wait_for_timeout(3000)
        body_text = self.page.locator("body").inner_text(timeout=5000)
        if "账号登录" in body_text and "登录/注册" in body_text:
            raise AssertionError(f"仍停留在登录页，未进入企业控制台：{body_text[:500]}")

    def click_sidebar_parent(self, text: str) -> None:
        box = self.sidebar_box(text, parent=True)
        self.page.mouse.click(box["x"] + box["width"] - 16, box["y"] + box["height"] / 2)
        self.page.wait_for_load_state("domcontentloaded")
        self.page.wait_for_timeout(1000)

    def click_sidebar_leaf(self, text: str) -> None:
        box = self.sidebar_box(text, parent=False)
        self.page.mouse.click(box["x"] + box["width"] / 2, box["y"] + box["height"] / 2)
        self.page.wait_for_load_state("domcontentloaded")
        self.page.wait_for_timeout(1000)

    def click_sidebar_text(self, text: str, right_edge: bool = False) -> None:
        box = self.sidebar_box(text, parent=right_edge)
        x = box["x"] + box["width"] - 16 if right_edge else box["x"] + box["width"] / 2
        self.page.mouse.click(x, box["y"] + box["height"] / 2)
        self.page.wait_for_load_state("domcontentloaded")
        self.page.wait_for_timeout(1000)

    def sidebar_box(self, text: str, parent: bool) -> dict:
        box = self.page.evaluate(
            """({ text, parent }) => {
                const nodes = Array.from(document.querySelectorAll('taro-view-core, div, span'));
                const rows = nodes
                  .map(el => ({ text: (el.innerText || el.textContent || '').trim(), r: el.getBoundingClientRect() }))
                  .filter(item => item.r.x >= 0 && item.r.x < 210 && item.r.y > 40)
                  .filter(item => item.r.width > 20 && item.r.width < 210 && item.r.height > 14)
                  .filter(item => item.text === text || item.text.split(/\\s+/).includes(text))
                  .filter(item => parent ? item.r.height >= 30 : true)
                  .sort((a, b) => {
                    if (a.text === text && b.text !== text) return -1;
                    if (b.text === text && a.text !== text) return 1;
                    return (a.r.width * a.r.height) - (b.r.width * b.r.height);
                  });
                const target = rows[0];
                if (!target) return null;
                return { x: target.r.x, y: target.r.y, width: target.r.width, height: target.r.height };
            }""",
            {"text": text, "parent": parent},
        )
        if not box:
            body_text = self.page.locator("body").inner_text(timeout=5000)
            raise AssertionError(f"未找到左侧菜单 {text}，当前页面文本：{body_text[:1000]}")
        return box

    def auth_key_frame(self) -> Frame:
        return self.frame_by_url_part("/secret")

    def my_auth_frame(self) -> Frame:
        return self.frame_by_url_part("/myAuth")

    def frame_by_url_part(self, url_part: str) -> Frame:
        for frame in self.page.frames:
            if url_part in frame.url:
                return frame
        urls = [frame.url for frame in self.page.frames]
        raise AssertionError(f"未找到授权 iframe {url_part}，当前 frames：{urls}")

    def generate_wanyouji_key(self) -> str:
        self.open_enterprise_auth_menu("授权密钥")
        frame = self.auth_key_frame()
        before_keys = set(self.extract_auth_keys(frame.locator("body").inner_text(timeout=5000)))

        self.click_frame_button(frame, "生成授权密钥")
        self.select_modal_option(frame, 0, "丸友集")
        selected_text = frame.locator(".ant-modal .ant-select-selector").first.inner_text(timeout=3000)
        assert "丸友集" in selected_text, f"生成授权密钥弹窗未成功选择丸友集，当前选择：{selected_text}"
        self.click_frame_button(frame, "生成密钥")
        self.page.wait_for_timeout(3000)

        text = frame.locator("body").inner_text(timeout=5000)
        after_keys = self.extract_auth_keys(text)
        new_keys = [key for key in after_keys if key not in before_keys]
        key = new_keys[0] if new_keys else self.extract_latest_table_key(frame)
        if not key:
            raise AssertionError(f"生成授权密钥后未在表格中找到新增密钥，当前页面文本：{text[:1200]}")
        return key

    def activate_key_for_wanzhanggui(self, auth_key: str) -> None:
        self.open_enterprise_auth_menu("我的授权")
        frame = self.my_auth_frame()
        if auth_key in frame.locator("body").inner_text(timeout=5000):
            return

        self.click_frame_button(frame, "激活密钥")
        self.select_modal_option(frame, 0, "丸掌柜")
        selected_text = frame.locator(".ant-modal .ant-select-selector").first.inner_text(timeout=3000)
        assert "丸掌柜" in selected_text, f"激活密钥弹窗未成功选择丸掌柜，当前选择：{selected_text}"
        frame.locator(".ant-modal input[placeholder='请输入授权密钥']").fill(auth_key)
        expect(frame.locator(".ant-modal input[placeholder='请输入授权密钥']")).to_have_value(auth_key, timeout=3000)
        self.click_frame_button(frame, "验证密钥")
        self.confirm_modal_until_closed_or_key_visible(frame, auth_key)
        self.click_optional_frame_buttons(frame, ["查 询", "查询"])
        self.page.wait_for_timeout(2000)

        text = frame.locator("body").inner_text(timeout=5000)
        duplicate_message = "该提供方的产品授权已绑定，请勿重复绑定"
        assert duplicate_message not in text, (
            f"丸掌柜未新增本次激活密钥 {auth_key}，页面提示：{duplicate_message}。"
            f"当前页面文本：{text[:1200]}"
        )
        assert auth_key in text, f"丸掌柜我的授权未新增本次激活密钥 {auth_key}，当前页面文本：{text[:1200]}"

    def confirm_modal_until_closed_or_key_visible(self, frame: Frame, auth_key: str) -> None:
        for _ in range(8):
            body_text = frame.locator("body").inner_text(timeout=5000)
            if auth_key in body_text and not frame.locator(".ant-modal").count():
                return

            clicked = frame.evaluate(
                """() => {
                    const modals = Array.from(document.querySelectorAll('.ant-modal'))
                      .filter(modal => {
                        const r = modal.getBoundingClientRect();
                        return r.width > 0 && r.height > 0;
                      });
                    const modal = modals[modals.length - 1];
                    if (!modal) return false;
                    const buttons = Array.from(modal.querySelectorAll('button'))
                      .filter(button => {
                        const text = (button.innerText || button.textContent || '').trim();
                        const r = button.getBoundingClientRect();
                        const cls = String(button.className || '');
                        return r.width > 0 && r.height > 0 &&
                          !button.disabled &&
                          text !== '取 消' &&
                          text !== '取消' &&
                          (cls.includes('ant-btn-primary') || /确定|确 定|保存|保 存|提交|提 交|激活|验证密钥/.test(text));
                      });
                    const target = buttons[buttons.length - 1];
                    if (!target) return false;
                    target.click();
                    return true;
                }"""
            )
            self.page.wait_for_timeout(1500)
            if not clicked:
                break

    def verify_and_save_wanyouji_permissions(self, auth_key: str) -> None:
        self.open_enterprise_auth_menu("授权密钥")
        frame = self.auth_key_frame()
        body_text = frame.locator("body").inner_text(timeout=5000)
        assert auth_key in body_text, f"丸友集授权密钥页未找到已生成密钥 {auth_key}，当前页面文本：{body_text[:1200]}"

        self.click_row_action(frame, auth_key, "管理授权")
        self.page.wait_for_timeout(2500)
        text = frame.locator("body").inner_text(timeout=5000)
        assert "福通互通" in text, f"管理授权中未出现福通互通功能权限，当前页面文本：{text[:1500]}"
        self.check_all_visible_permissions(frame)
        self.check_permissions_by_label(frame, ["福通互通", "订单基本信息", "修改订单", "创建订单"])
        unchecked = self.unchecked_permission_labels(frame)
        assert not unchecked, f"丸友集管理授权保存前仍有未勾选权限：{unchecked}"
        self.click_optional_frame_buttons(frame, ["保 存", "保存", "确 定", "确定"])

    def verify_wanzhanggui_authorized_permissions(self, auth_key: str, required_permissions: list[str]) -> None:
        self.open_enterprise_auth_menu("我的授权")
        frame = self.my_auth_frame()
        body_text = frame.locator("body").inner_text(timeout=5000)
        assert auth_key in body_text, f"丸掌柜我的授权页未找到已激活密钥 {auth_key}，当前页面文本：{body_text[:1200]}"

        self.click_row_action(frame, auth_key, "查看")
        self.page.wait_for_timeout(2500)
        detail_text = frame.locator("body").inner_text(timeout=5000)
        for permission in required_permissions:
            assert permission in detail_text, f"丸掌柜授权详情未展示权限「{permission}」，当前详情文本：{detail_text[:1500]}"
            assert self.permission_checked(frame, permission), f"丸掌柜授权详情中权限「{permission}」未勾选，当前详情文本：{detail_text[:1500]}"

    def click_frame_button(self, frame: Frame, text: str) -> None:
        buttons = frame.locator("button").filter(has_text=text)
        try:
            expect(buttons.first).to_be_visible(timeout=5000)
            buttons.first.click(timeout=5000)
        except Exception:
            body_text = frame.locator("body").inner_text(timeout=5000)
            raise AssertionError(f"未点击到按钮 {text}，当前 iframe 文本：{body_text[:1000]}")
        self.page.wait_for_timeout(1000)

    def click_optional_frame_buttons(self, frame: Frame, texts: list[str]) -> bool:
        for text in texts:
            try:
                self.click_frame_button(frame, text)
                return True
            except AssertionError:
                continue
        return False

    def select_modal_option(self, frame: Frame, select_index: int, option_text: str) -> None:
        frame.locator(".ant-modal .ant-select-selector").nth(select_index).click()
        self.page.wait_for_timeout(800)
        option = frame.locator(".ant-select-dropdown:not(.ant-select-dropdown-hidden) .ant-select-item-option-content").filter(
            has_text=option_text
        ).first
        expect(option).to_be_visible(timeout=5000)
        option.click()
        self.page.wait_for_timeout(800)

    def permission_checked(self, frame: Frame, permission: str) -> bool:
        return bool(
            frame.evaluate(
                """permission => {
                    const wrappers = Array.from(document.querySelectorAll('.ant-checkbox-wrapper, label'));
                    return wrappers.some(el => {
                        const text = (el.innerText || el.textContent || '').trim();
                        if (!text.includes(permission)) return false;
                        const input = el.querySelector('input[type="checkbox"]');
                        const checkbox = el.querySelector('.ant-checkbox') || el;
                        return Boolean(input?.checked) ||
                            String(checkbox.className).includes('ant-checkbox-checked') ||
                            String(el.className).includes('ant-checkbox-wrapper-checked');
                    });
                }""",
                permission,
            )
        )

    def click_row_action(self, frame: Frame, row_key: str, action_text: str) -> None:
        row = frame.locator(".ant-table-tbody tr").filter(has_text=row_key).first
        try:
            expect(row).to_be_visible(timeout=5000)
            row.get_by_text(action_text, exact=True).click(timeout=5000, force=True)
            self.page.wait_for_timeout(1000)
            return
        except Exception:
            text = frame.locator("body").inner_text(timeout=5000)
            raise AssertionError(f"未在密钥行点击 {action_text}，密钥：{row_key}，页面文本：{text[:1200]}")

    def check_all_visible_permissions(self, frame: Frame) -> None:
        for _ in range(3):
            frame.evaluate(
                """() => {
                    const expanders = Array.from(document.querySelectorAll(
                        '.ant-tree-switcher_close, .ant-tree-switcher:not(.ant-tree-switcher-noop)'
                    ));
                    expanders.forEach(el => {
                        if (String(el.className).includes('close')) el.click();
                    });
                }"""
            )
            self.page.wait_for_timeout(500)
            clicked = frame.evaluate(
                """() => {
                    let clicked = 0;
                    const wrappers = Array.from(document.querySelectorAll(
                        '.ant-checkbox-wrapper:not(.ant-checkbox-wrapper-disabled), .ant-tree-checkbox:not(.ant-tree-checkbox-disabled), .ant-checkbox:not(.ant-checkbox-disabled)'
                    ));
                    wrappers.forEach(wrapper => {
                        const text = (wrapper.innerText || wrapper.textContent || '').trim();
                        const input = wrapper.querySelector('input[type="checkbox"]');
                        const checked = Boolean(input?.checked) ||
                            String(wrapper.className).includes('ant-checkbox-wrapper-checked') ||
                            String(wrapper.className).includes('ant-checkbox-checked') ||
                            String(wrapper.className).includes('ant-tree-checkbox-checked');
                        const disabled = Boolean(input?.disabled) ||
                            String(wrapper.className).includes('disabled');
                        if (!checked && !disabled) {
                            const target = input || wrapper;
                            target.click();
                            clicked += 1;
                        }
                    });
                    return clicked;
                }"""
            )
            self.page.wait_for_timeout(800)
            if clicked == 0:
                break

    def check_permissions_by_label(self, frame: Frame, permissions: list[str]) -> None:
        for permission in permissions:
            for _ in range(4):
                checked = self.permission_checked(frame, permission)
                if checked:
                    break
                clicked = frame.evaluate(
                    """permission => {
                        const nodes = Array.from(document.querySelectorAll(
                            '.ant-checkbox-wrapper, .ant-tree-treenode, label, div, span'
                        ));
                        const candidates = nodes
                          .map(el => ({ el, text: (el.innerText || el.textContent || '').trim(), r: el.getBoundingClientRect() }))
                          .filter(item => item.r.width > 0 && item.r.height > 0)
                          .filter(item => item.text.includes(permission))
                          .sort((a, b) => (a.r.width * a.r.height) - (b.r.width * b.r.height));
                        for (const item of candidates) {
                          const container = item.el.closest('.ant-tree-treenode, label, .ant-checkbox-wrapper, .ant-row, div') || item.el;
                          const checkbox = container.querySelector(
                            '.ant-checkbox:not(.ant-checkbox-disabled), .ant-tree-checkbox:not(.ant-tree-checkbox-disabled), input[type="checkbox"]:not(:disabled)'
                          );
                          if (!checkbox) continue;
                          const input = checkbox.matches('input') ? checkbox : checkbox.querySelector('input[type="checkbox"]');
                          const checked = Boolean(input?.checked) ||
                            String(checkbox.className).includes('ant-checkbox-checked') ||
                            String(checkbox.className).includes('ant-tree-checkbox-checked') ||
                            String(container.className).includes('ant-checkbox-wrapper-checked');
                          if (!checked) {
                            (input || checkbox).click();
                            return true;
                          }
                        }
                        return false;
                    }""",
                    permission,
                )
                self.page.wait_for_timeout(700)
                if not clicked:
                    break
            assert self.permission_checked(frame, permission), f"权限「{permission}」未能勾选成功"

    def unchecked_permission_labels(self, frame: Frame) -> list[str]:
        return list(
            frame.evaluate(
                """() => {
                    const labels = [];
                    const wrappers = Array.from(document.querySelectorAll(
                        '.ant-checkbox-wrapper:not(.ant-checkbox-wrapper-disabled), .ant-tree-checkbox:not(.ant-tree-checkbox-disabled), .ant-checkbox:not(.ant-checkbox-disabled)'
                    ));
                    wrappers.forEach(wrapper => {
                        const rect = wrapper.getBoundingClientRect();
                        if (!rect.width || !rect.height) return;
                        const input = wrapper.querySelector('input[type="checkbox"]');
                        const checked = Boolean(input?.checked) ||
                            String(wrapper.className).includes('ant-checkbox-wrapper-checked') ||
                            String(wrapper.className).includes('ant-checkbox-checked') ||
                            String(wrapper.className).includes('ant-tree-checkbox-checked');
                        const disabled = Boolean(input?.disabled) ||
                            String(wrapper.className).includes('disabled');
                        if (checked || disabled) return;
                        const text = (wrapper.innerText || wrapper.textContent || '').trim();
                        const row = wrapper.closest('label, .ant-tree-treenode, .ant-form-item, .ant-row, div');
                        const rowText = (row?.innerText || row?.textContent || '').trim();
                        labels.push(text || rowText || '<unknown>');
                    });
                    return Array.from(new Set(labels)).filter(Boolean);
                }"""
            )
        )
        self.page.wait_for_timeout(800)

    def extract_auth_key(self, text: str) -> str:
        matches = self.extract_auth_keys(text)
        for match in matches:
            if match not in {
                "6c829f6d43964a089078a2fabd6ae45c",
                "cdec2858dd07491d8d1c0326b9af7eef",
                "78d8fc0ce6e244b89089eb68083ecb27",
                "2fca606cc7ac45a39929ca54fa428426",
            }:
                return match
        return matches[0] if matches else ""

    def extract_auth_keys(self, text: str) -> list[str]:
        return re.findall(r"\b[a-f0-9]{32}\b", text)

    def extract_latest_table_key(self, frame: Frame) -> str:
        return str(
            frame.evaluate(
                """() => {
                    const rows = Array.from(document.querySelectorAll('.ant-table-tbody tr'));
                    for (const row of rows) {
                        const text = row.innerText || row.textContent || '';
                        const match = text.match(/\\b[a-f0-9]{32}\\b/);
                        if (match) return match[0];
                    }
                    return '';
                }"""
            )
        )
