from __future__ import annotations

from pathlib import Path
from typing import Iterable

from playwright.sync_api import Frame, Page, expect

from e2e.pages.tenant_auth_page import TenantAuthPage
from e2e.pages.wanzhanggui_order_page import WANYOUJI_CONSOLE_URL
from e2e.pages.wanzhanggui_push_page import WANZHANGGUI_CONSOLE_URL
from e2e.utils.bug_reporter import BugReporter


class AuthorizationSimplificationPage(TenantAuthPage):
    """Interactions shared by the new Wanyouji/Wanzhanggui authorization pages."""

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

    def open_wanyouji_merchant_recommendation(self) -> Frame:
        required = ["发单商户推荐", "发单商户授权", "待我审核"]
        last_error: AssertionError | None = None
        for _ in range(3):
            try:
                self.goto(WANYOUJI_CONSOLE_URL)
                self.wait_for_enterprise_console_shell()
                self.page.wait_for_timeout(1200)
                self.click_sidebar_text("工作室基础应用", right_edge=True)
                self.click_sidebar_text("发单商户推荐")
                return self._business_frame(required)
            except AssertionError as error:
                last_error = error
                self.page.wait_for_timeout(1200)
        raise last_error or AssertionError("未能加载发单商户推荐页面")

    def open_wanzhanggui_workshop_authorization_new(self) -> Frame:
        required = ["工作室推荐", "工作室授权", "待我审核"]
        last_error: AssertionError | None = None
        for _ in range(3):
            try:
                self.goto(WANZHANGGUI_CONSOLE_URL)
                self.wait_for_enterprise_console_shell()
                self.page.wait_for_timeout(1200)
                self.click_sidebar_text("商家管理", right_edge=True)
                self.click_sidebar_text("工作室授权新")
                return self._business_frame(required)
            except AssertionError as error:
                last_error = error
                self.page.wait_for_timeout(1200)
        raise last_error or AssertionError("未能加载工作室授权新页面")

    def _business_frame(self, required_texts: Iterable[str]) -> Frame:
        required = list(required_texts)
        for _ in range(36):
            for frame in reversed(self.page.frames):
                try:
                    body = frame.locator("body").inner_text(timeout=1200)
                except Exception:
                    continue
                if all(text in body for text in required):
                    return frame
            self.page.wait_for_timeout(800)
        snapshots: list[str] = []
        for frame in self.page.frames:
            try:
                snapshots.append(f"{frame.url}: {frame.locator('body').inner_text(timeout=600)[:600]}")
            except Exception:
                continue
        raise AssertionError(f"未找到授权简化业务 iframe；required={required}; frames={snapshots}")

    def click_tab(self, frame: Frame, tab_text: str) -> None:
        clicked = frame.evaluate(
            """text => {
                const visible = el => { const r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0; };
                const compact = value => (value || '').replace(/\s+/g, '').trim();
                const nodes = Array.from(document.querySelectorAll('[role=tab], .ant-tabs-tab, button, a, span, div'))
                    .filter(visible)
                    .filter(el => {
                        const value = compact(el.innerText || el.textContent || '');
                        return value === compact(text) || value.startsWith(compact(text));
                    });
                const target = nodes.sort((a, b) => {
                    const ar = a.getBoundingClientRect(), br = b.getBoundingClientRect();
                    return ar.width * ar.height - br.width * br.height;
                })[0];
                if (!target) return false;
                (target.closest('[role=tab], .ant-tabs-tab, button, a') || target).click();
                return true;
            }""",
            tab_text,
        )
        assert clicked, f"授权页未找到Tab {tab_text}；text={frame.locator('body').inner_text(timeout=5000)[:1500]}"
        self.page.wait_for_timeout(900)

    def search_by_name(self, frame: Frame, label: str, value: str) -> None:
        found = frame.evaluate(
            """({ label, value }) => {
                const visible = el => { const r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0; };
                const inputs = Array.from(document.querySelectorAll('input')).filter(visible);
                const target = inputs.find(input => {
                    const placeholder = input.getAttribute('placeholder') || '';
                    const scope = input.parentElement?.parentElement?.innerText || '';
                    return placeholder.includes(label) || scope.includes(label);
                }) || inputs[0];
                if (!target) return false;
                const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
                setter?.call(target, value);
                target.dispatchEvent(new InputEvent('input', { bubbles: true, data: value, inputType: 'insertText' }));
                target.dispatchEvent(new Event('change', { bubbles: true }));
                return true;
            }""",
            {"label": label, "value": value},
        )
        assert found, f"授权页未找到 {label} 搜索框"
        self._click_text(frame, ["查询", "查 询"])
        self.page.wait_for_timeout(1400)

    def row_text(self, frame: Frame, name: str) -> str:
        return str(
            frame.evaluate(
                """name => {
                    const visible = el => { const r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0; };
                    const rows = Array.from(document.querySelectorAll('tr, .ant-table-row, [class*=table-row]'))
                      .filter(visible)
                      .map(row => (row.innerText || row.textContent || '').trim());
                    return rows.find(text => text.includes(name)) || '';
                }""",
                name,
            )
        )

    def require_row(self, frame: Frame, name: str, expected_text: str = "") -> str:
        for _ in range(5):
            text = self.row_text(frame, name)
            if text and (not expected_text or expected_text in text):
                return text
            self.page.wait_for_timeout(900)
        body = frame.locator("body").inner_text(timeout=5000)
        raise AssertionError(f"授权页未找到记录或状态不匹配；name={name}; expected={expected_text}; text={body[:1800]}")

    def click_row_action(self, frame: Frame, name: str, actions: list[str]) -> None:
        clicked = frame.evaluate(
            """({ name, actions }) => {
                const visible = el => { const r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0; };
                const rows = Array.from(document.querySelectorAll('tr, .ant-table-row, [class*=table-row]'))
                  .filter(visible)
                  .filter(row => (row.innerText || row.textContent || '').includes(name));
                for (const row of rows) {
                  const controls = Array.from(row.querySelectorAll('button, a, [role=button], span, div')).filter(visible);
                  const target = controls.find(el => actions.some(action => (el.innerText || el.textContent || '').trim() === action));
                  if (target) { (target.closest('button, a, [role=button]') || target).click(); return true; }
                }
                return false;
            }""",
            {"name": name, "actions": actions},
        )
        assert clicked, f"授权页记录未找到操作；name={name}; actions={actions}; row={self.row_text(frame, name)}"
        self.page.wait_for_timeout(700)

    def confirm_visible_modal(self, frame: Frame, actions: list[str] | None = None) -> None:
        action_texts = actions or ["同意", "确认", "确定", "保存"]
        clicked = frame.evaluate(
            """actions => {
                const visible = el => { const r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0; };
                const roots = Array.from(document.querySelectorAll('.ant-modal, .ant-drawer, [role=dialog]')).filter(visible);
                const root = roots[roots.length - 1] || document;
                const controls = Array.from(root.querySelectorAll('button, [role=button], span, div')).filter(visible);
                const compact = value => (value || '').replace(/\s+/g, '').trim();
                const target = controls.find(el => actions.some(action => compact(el.innerText || el.textContent || '') === compact(action)));
                if (!target) return false;
                (target.closest('button, [role=button]') || target).click();
                return true;
            }""",
            action_texts,
        )
        assert clicked, f"未找到确认操作 {action_texts}；modal={frame.locator('body').inner_text(timeout=5000)[:1500]}"
        self.page.wait_for_timeout(1200)

    def click_display_config(self, frame: Frame) -> None:
        for _ in range(3):
            self._click_text(frame, ["对外展示配置"])
            dialog = frame.locator(".ant-modal:visible, [role=dialog]:visible").last
            try:
                expect(dialog).to_be_visible(timeout=3000)
                return
            except AssertionError:
                self.page.wait_for_timeout(500)
        raise AssertionError("未能打开对外展示配置弹窗")

    def save_display_config(self, frame: Frame, data: dict[str, str], qr_code: Path | None = None) -> None:
        root = frame.locator(".ant-modal:visible, [role=dialog]:visible").last
        expect(root).to_be_visible(timeout=5000)
        self._fill_modal_field(root, ["简介"], data["intro"], textarea=True)
        self._select_modal_option(frame, root, "擅长游戏", data["game"])
        self._select_modal_option(frame, root, "结算周期", data["settlement"])
        self._fill_modal_field(root, ["联系人"], data["contact"])
        self._fill_modal_field(root, ["联系手机", "手机号"], data["phone"])
        self._fill_modal_field(root, ["微信号"], data["wechat"])
        self._fill_modal_field(root, ["QQ号", "QQ 号"], data["qq"])
        if qr_code:
            upload = root.locator("input[type=file]").first
            if upload.count():
                upload.set_input_files(str(qr_code))
                uploading = root.get_by_text("Uploading...", exact=False)
                if uploading.count():
                    expect(uploading).to_be_hidden(timeout=30000)
        self.confirm_visible_modal(frame, ["保存"])
        expect(root).to_be_hidden(timeout=8000)

    def toggle_external_display(self, frame: Frame, enabled: bool = True) -> None:
        result = frame.evaluate(
            """enabled => {
                const visible = el => { const r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0; };
                const label = Array.from(document.querySelectorAll('span, div, label')).find(el => visible(el) && (el.innerText || el.textContent || '').trim() === '对外展示');
                if (!label) return false;
                const scope = label.parentElement?.parentElement || label.parentElement || document;
                const input = scope.querySelector('input[type=checkbox]');
                if (input && Boolean(input.checked) !== enabled) { input.click(); return true; }
                const control = scope.querySelector('.ant-switch, [role=switch]');
                if (control) { control.click(); return true; }
                return Boolean(input?.checked) === enabled;
            }""",
            enabled,
        )
        assert result, "未找到或未能切换对外展示开关"
        self.page.wait_for_timeout(900)

    def _fill_modal_field(self, root, labels: list[str], value: str, *, textarea: bool = False) -> None:
        selector = "textarea" if textarea else "input"
        for label in labels:
            candidate = root.locator(f"{selector}[placeholder*='{label}']").first
            if candidate.count():
                candidate.fill(value)
                return
        inputs = root.locator(selector)
        for index in range(inputs.count()):
            candidate = inputs.nth(index)
            try:
                belongs_to_label = candidate.evaluate(
                    """(element, labels) => {
                        for (let node = element.parentElement; node && node !== document.body; node = node.parentElement) {
                            const text = node.innerText || node.textContent || '';
                            const controls = node.querySelectorAll('input, textarea');
                            if (controls.length === 1 && labels.some(label => text.includes(label))) return true;
                        }
                        return false;
                    }""",
                    labels,
                )
            except Exception:
                belongs_to_label = False
            if belongs_to_label:
                candidate.fill(value)
                return
        controls = root.evaluate(
            """node => Array.from(node.querySelectorAll('input, textarea, [contenteditable=true]'))
              .map(el => ({ tag: el.tagName, placeholder: el.getAttribute('placeholder') || '', name: el.getAttribute('name') || '' }))"""
        )
        raise AssertionError(
            f"展示配置弹窗未找到字段 {labels}; text={root.inner_text(timeout=3000)[:1200]}; controls={controls}"
        )

    def _select_modal_option(self, frame: Frame, root, label: str, value: str) -> None:
        comboboxes = root.get_by_role("combobox")
        index = 0 if label == "擅长游戏" else 1
        if comboboxes.count() > index:
            combobox = comboboxes.nth(index)
            combobox.click(timeout=3000, force=True)
            self.page.wait_for_timeout(500)
            option = self.page.locator(f'[role="option"][aria-label="{value}"]:visible').first
            try:
                expect(option).to_be_visible(timeout=1500)
                option.click()
            except AssertionError:
                combobox.press("ArrowDown")
                combobox.press("Enter")
            combobox.press("Escape")
            self.page.wait_for_timeout(300)
            return

        selected = root.evaluate(
            r"""(root, { label, value }) => {
                const norm = text => String(text || '').replace(/\s+/g, '').trim();
                const labels = Array.from(root.querySelectorAll('*'))
                  .map(el => ({ el, text: norm(el.innerText || el.textContent || '') }))
                  .filter(item => item.text === norm(label))
                  .sort((a, b) => a.text.length - b.text.length);
                const target = labels[0]?.el;
                if (!target) return false;
                for (let node = target.parentElement; node; node = node.parentElement) {
                  const selector = node.querySelector('.ant-select-selector, [role=combobox], input:not([type=file])');
                  if (selector) {
                    selector.click();
                    return true;
                  }
                }
                return false;
            }""",
            {"label": label, "value": value},
        )
        if not selected:
            structure = root.evaluate(
                r"""(root, label) => Array.from(root.querySelectorAll('*'))
                  .filter(el => (el.innerText || el.textContent || '').trim() === label)
                  .slice(0, 3)
                  .map(el => {
                    const items = [];
                    for (let node = el; node && items.length < 5; node = node.parentElement) {
                      items.push({ tag: node.tagName, className: String(node.className || ''), text: (node.innerText || node.textContent || '').trim().slice(0, 300) });
                    }
                    return items;
                  })""",
                label,
            )
            raise AssertionError(f"展示配置弹窗未找到下拉字段 {label}; structure={structure}")
        self.page.wait_for_timeout(500)
        option = frame.locator(".ant-select-dropdown:not(.ant-select-dropdown-hidden) .ant-select-item-option-content").filter(has_text=value).first
        expect(option).to_be_visible(timeout=4000)
        option.click()
        self.page.wait_for_timeout(300)

    def _click_text(self, frame: Frame, texts: list[str]) -> None:
        clicked = frame.evaluate(
            """texts => {
                const visible = el => { const r = el.getBoundingClientRect(); return r.width > 0 && r.height > 0; };
                const nodes = Array.from(document.querySelectorAll('button, a, [role=button], span, div')).filter(visible);
                const target = nodes.find(el => texts.some(text => (el.innerText || el.textContent || '').trim() === text));
                if (!target) return false;
                (target.closest('button, a, [role=button]') || target).click();
                return true;
            }""",
            texts,
        )
        assert clicked, f"未找到操作 {texts}；text={frame.locator('body').inner_text(timeout=5000)[:1500]}"
