from __future__ import annotations

import time
from dataclasses import dataclass

from playwright.sync_api import Frame, Page, expect

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


@dataclass(frozen=True)
class DictionaryOption:
    dictionary_name: str
    option_name: str
    sort_weight: str = "1"
    display_color: str = "#000000"
    resolve_minutes: str = "240"


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

    def open_data_dictionary(self) -> Frame:
        self.goto(WANZHANGGUI_CONSOLE_URL)
        self.wait_for_enterprise_console_shell()
        self.click_sidebar_text("商家管理", right_edge=True)
        for _ in range(4):
            clicked = self.click_sidebar_text_if_present("数据字典") or self.click_sidebar_text_if_present("字典管理")
            assert clicked, f"左侧菜单未找到数据字典入口；text={self.page.locator('body').inner_text(timeout=5000)[:1200]}"
            frame = self.wait_for_data_dictionary_frame(timeout_ms=5000)
            if frame:
                return frame
        return self.frame_with_text(["数据字典", "字典名称", "字典编码", "工单"])

    def wait_for_data_dictionary_frame(self, timeout_ms: int = 5000) -> Frame | None:
        deadline = time.monotonic() + timeout_ms / 1000
        while time.monotonic() < deadline:
            for frame in self.page.frames:
                if "/dataDict" in frame.url:
                    return frame
            self.page.wait_for_timeout(300)
        return None

    def ensure_dictionary_options(self, options: list[DictionaryOption]) -> None:
        frame = self.open_data_dictionary()
        for option in options:
            self.ensure_dictionary_option(frame, option)

    def ensure_dictionary_option(self, frame: Frame, option: DictionaryOption) -> None:
        self.switch_dictionary_tab(frame, option.dictionary_name)
        self.search_dictionary_item(frame, option.option_name)
        if self.dictionary_item_exists(frame, option.option_name):
            return
        self.click_frame_text(frame, ["添加选项"])
        self.fill_dictionary_item_modal(frame, option)
        self.click_modal_button(frame, ["保 存", "保存", "确 定", "确定", "确认", "提交"])
        self.page.wait_for_timeout(1500)
        self.search_dictionary_item(frame, option.option_name)
        assert self.dictionary_item_exists(frame, option.option_name), (
            f"新增字典选项后未找到 {option.option_name}；dictionary={option.dictionary_name}; "
            f"text={frame.locator('body').inner_text(timeout=5000)[:1500]}"
        )

    def switch_dictionary_tab(self, frame: Frame, dictionary_name: str) -> None:
        assert self.click_frame_text(frame, [dictionary_name]), (
            f"数据字典页未找到分类 {dictionary_name}；text={frame.locator('body').inner_text(timeout=5000)[:1500]}"
        )
        self.page.wait_for_timeout(800)
        assert dictionary_name in frame.locator("body").inner_text(timeout=5000), (
            f"切换数据字典分类后未看到 {dictionary_name}；text={frame.locator('body').inner_text(timeout=5000)[:1500]}"
        )

    def search_dictionary_item(self, frame: Frame, option_name: str) -> None:
        self.fill_filter_input_by_placeholder(frame, "请输入字典值", option_name)
        self.click_frame_text(frame, ["查询", "查 询", "搜索"])
        self.page.wait_for_timeout(1500)

    def open_dictionary_items(self, frame: Frame, dictionary_name: str) -> None:
        clicked = frame.evaluate(
            """({ dictionaryName }) => {
                const rows = Array.from(document.querySelectorAll('tr, .ant-table-row'))
                  .map(row => ({ row, text: (row.innerText || row.textContent || '').trim(), r: row.getBoundingClientRect() }))
                  .filter(item => item.r.width > 0 && item.r.height > 0)
                  .filter(item => item.text.includes(dictionaryName));
                const row = rows[0]?.row;
                if (!row) return false;
                const actions = Array.from(row.querySelectorAll('button, a, span'))
                  .map(el => ({ el, text: (el.innerText || el.textContent || '').trim(), r: el.getBoundingClientRect() }))
                  .filter(item => item.r.width > 0 && item.r.height > 0);
                const target = actions.find(item => ['字典项', '选项', '详情', '编辑', '配置'].some(text => item.text.includes(text))) ||
                  actions[actions.length - 1];
                if (!target) return false;
                target.el.click();
                return true;
            }""",
            {"dictionaryName": dictionary_name},
        )
        assert clicked, f"未找到 {dictionary_name} 的字典项/编辑入口；text={frame.locator('body').inner_text(timeout=5000)[:1500]}"
        self.page.wait_for_timeout(1500)

    def dictionary_item_exists(self, frame: Frame, option_name: str) -> bool:
        text = self.visible_modal_text(frame) or frame.locator("body").inner_text(timeout=5000)
        return option_name in text

    def fill_dictionary_item_modal(self, frame: Frame, option: DictionaryOption) -> None:
        frame.evaluate(
            """({ optionName, sortWeight, displayColor, resolveMinutes }) => {
                const roots = Array.from(document.querySelectorAll('.ant-modal'))
                  .filter(el => {
                    const r = el.getBoundingClientRect();
                    return r.width > 0 && r.height > 0;
                  });
                const root = roots.pop() || document;
                const controls = Array.from(root.querySelectorAll('input, textarea'))
                  .filter(el => el.getAttribute('type') !== 'hidden' && !el.disabled && !el.readOnly)
                  .filter(el => {
                    const r = el.getBoundingClientRect();
                    return r.width > 0 && r.height > 0;
                  });
                const setValue = (el, value) => {
                    const proto = el.tagName === 'TEXTAREA' ? window.HTMLTextAreaElement.prototype : window.HTMLInputElement.prototype;
                    const setter = Object.getOwnPropertyDescriptor(proto, 'value').set;
                    setter.call(el, value);
                    el.dispatchEvent(new InputEvent('input', { bubbles: true, inputType: 'insertText', data: value }));
                    el.dispatchEvent(new Event('change', { bubbles: true }));
                    el.dispatchEvent(new FocusEvent('blur', { bubbles: true }));
                };
                const nameControl = controls.find(el => /名称|标签|选项|字典项/.test(el.getAttribute('placeholder') || '')) || controls[0];
                if (nameControl) setValue(nameControl, optionName);
                for (const control of controls) {
                    const placeholder = control.getAttribute('placeholder') || '';
                    if (/如：240/.test(placeholder)) setValue(control, resolveMinutes);
                    if (/数字越小越靠前/.test(placeholder)) setValue(control, sortWeight);
                }
                const colorInput = controls.find(el => /^#[0-9a-fA-F]{6}$/.test(el.value || ''));
                if (colorInput) setValue(colorInput, displayColor);
            }""",
            {
                "optionName": option.option_name,
                "sortWeight": option.sort_weight,
                "displayColor": option.display_color,
                "resolveMinutes": option.resolve_minutes,
            },
        )
        self.page.wait_for_timeout(500)

    def fill_first_visible_filter_input(self, frame: Frame, value: str) -> bool:
        return bool(
            frame.evaluate(
                """value => {
                    const inputs = Array.from(document.querySelectorAll('input'))
                      .filter(el => el.getAttribute('type') !== 'hidden' && !el.disabled)
                      .map(el => ({ el, placeholder: el.getAttribute('placeholder') || '', r: el.getBoundingClientRect() }))
                      .filter(item => item.r.width > 0 && item.r.height > 0)
                      .sort((a, b) => a.r.y - b.r.y || a.r.x - b.r.x);
                    const target = inputs.find(item => /字典|名称|编码|关键/.test(item.placeholder)) || inputs[0];
                    if (!target) return false;
                    const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
                    setter.call(target.el, value);
                    target.el.dispatchEvent(new InputEvent('input', { bubbles: true, inputType: 'insertText', data: value }));
                    target.el.dispatchEvent(new Event('change', { bubbles: true }));
                    return true;
                }""",
                value,
            )
        )

    def fill_filter_input_by_placeholder(self, frame: Frame, placeholder: str, value: str) -> bool:
        return bool(
            frame.evaluate(
                """({ placeholder, value }) => {
                    const inputs = Array.from(document.querySelectorAll('input'))
                      .filter(el => el.getAttribute('type') !== 'hidden' && !el.disabled)
                      .map(el => ({ el, placeholder: el.getAttribute('placeholder') || '', r: el.getBoundingClientRect() }))
                      .filter(item => item.r.width > 0 && item.r.height > 0)
                      .sort((a, b) => a.r.y - b.r.y || a.r.x - b.r.x);
                    const target = inputs.find(item => item.placeholder === placeholder) || inputs[1] || inputs[0];
                    if (!target) return false;
                    const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
                    setter.call(target.el, value);
                    target.el.dispatchEvent(new InputEvent('input', { bubbles: true, inputType: 'insertText', data: value }));
                    target.el.dispatchEvent(new Event('change', { bubbles: true }));
                    return true;
                }""",
                {"placeholder": placeholder, "value": value},
            )
        )

    def click_sidebar_text_if_present(self, text: str) -> bool:
        try:
            self.click_sidebar_text(text)
            return True
        except AssertionError:
            return False

    def frame_with_text(self, required_any_texts: list[str]) -> Frame:
        for frame in self.page.frames:
            if "/dataDict" in frame.url:
                return frame
        for frame in self.page.frames:
            if frame == self.page.main_frame:
                continue
            try:
                text = frame.locator("body").inner_text(timeout=2000)
            except Exception:
                continue
            if any(item in text for item in required_any_texts):
                return frame
        body_text = self.page.locator("body").inner_text(timeout=5000)
        urls = [frame.url for frame in self.page.frames]
        raise AssertionError(f"未找到数据字典 iframe；frames={urls}; body={body_text[:1200]}")

    def click_frame_text(self, frame: Frame, texts: list[str]) -> bool:
        for text in texts:
            clicked = frame.evaluate(
                """text => {
                    const nodes = Array.from(document.querySelectorAll('button, a, span, div'))
                      .map(el => ({ el, value: (el.innerText || el.textContent || '').trim(), r: el.getBoundingClientRect() }))
                      .filter(item => item.r.width > 0 && item.r.height > 0)
                      .filter(item => item.value === text || item.value.includes(text))
                      .sort((a, b) => {
                        const aExact = a.value === text ? 0 : 1;
                        const bExact = b.value === text ? 0 : 1;
                        if (aExact !== bExact) return aExact - bExact;
                        const aButton = ['BUTTON', 'A'].includes(a.el.tagName) ? 0 : 1;
                        const bButton = ['BUTTON', 'A'].includes(b.el.tagName) ? 0 : 1;
                        if (aButton !== bButton) return aButton - bButton;
                        return (a.r.width * a.r.height) - (b.r.width * b.r.height);
                      });
                    const target = nodes[0]?.el;
                    if (!target) return false;
                    target.click();
                    return true;
                }""",
                text,
            )
            if clicked:
                return True
        return False

    def click_modal_button(self, frame: Frame, texts: list[str]) -> None:
        assert self.click_frame_text(frame, texts), (
            f"弹窗/页面未找到按钮 {texts}；text={self.visible_modal_text(frame)[:1200] or frame.locator('body').inner_text(timeout=5000)[:1200]}"
        )
        self.page.wait_for_timeout(1000)

    def visible_modal_text(self, frame: Frame) -> str:
        return str(
            frame.evaluate(
                """() => {
                    const roots = Array.from(document.querySelectorAll('.ant-modal'))
                      .filter(el => {
                        const r = el.getBoundingClientRect();
                        return r.width > 0 && r.height > 0;
                      });
                    const root = roots.pop();
                    return root ? (root.innerText || root.textContent || '') : '';
                }"""
            )
        )

    def close_current_modal_if_present(self, frame: Frame) -> None:
        frame.evaluate(
            """() => {
                const roots = Array.from(document.querySelectorAll('.ant-modal'))
                  .filter(el => {
                    const r = el.getBoundingClientRect();
                    return r.width > 0 && r.height > 0;
                  });
                const root = roots.pop();
                const close = root?.querySelector('.ant-modal-close');
                close?.click();
            }"""
        )
        self.page.wait_for_timeout(800)
