from __future__ import annotations

import json
import os
import sys
from pathlib import Path

from dotenv import load_dotenv
from playwright.sync_api import sync_playwright

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))

from e2e.pages.tenant_auth_page import TenantAuthPage
from e2e.pages.wanzhanggui_push_page import WANZHANGGUI_CONSOLE_URL


OUT_DIR = Path("runtime/probes/wanzhanggui_data_dictionary")


def main() -> None:
    load_dotenv()
    OUT_DIR.mkdir(parents=True, exist_ok=True)
    with sync_playwright() as playwright:
        browser = playwright.chromium.launch(headless=False, slow_mo=100, args=["--window-size=1920,1080"])
        context = browser.new_context(viewport={"width": 1920, "height": 1080}, screen={"width": 1920, "height": 1080})
        page = context.new_page()
        auth_page = TenantAuthPage(page, os.getenv("TENANT_HOME_URL", "https://test-lianlianyun.lianlianwz.com/home"))
        auth_page.login_main_account(
            os.getenv("E2E_ORDER_WANZHANGGUI_PHONE", os.getenv("E2E_WANZHANGGUI_TENANT_PHONE", "19147509308")),
            os.getenv("E2E_ORDER_WANZHANGGUI_PASSWORD", os.getenv("E2E_WANZHANGGUI_TENANT_PASSWORD", "test123456")),
        )
        page.goto(WANZHANGGUI_CONSOLE_URL, wait_until="domcontentloaded")
        page.wait_for_timeout(3000)
        dump_page(page, "console_start")
        click_sidebar(page, "商家管理", right_edge=True)
        dump_page(page, "after_商家管理")
        clicked = click_sidebar(page, "数据字典") or click_sidebar(page, "字典管理")
        dump_page(page, "after_数据字典" if clicked else "data_dictionary_not_found")
        probe_frames(page, "data_dictionary")
        if clicked:
            frame = frame_with_keywords(page, ["数据字典", "字典名称", "字典编码", "工单"])
            if frame:
                probe_dictionary_pages(frame)
        browser.close()


def dump_page(page, name: str) -> None:
    state = {
        "url": page.url,
        "text": safe_text(lambda: page.locator("body").inner_text(timeout=5000)),
        "menus": page.evaluate(
            """() => Array.from(document.querySelectorAll('taro-view-core, div, span'))
              .map(el => ({ text: (el.innerText || el.textContent || '').trim(), r: el.getBoundingClientRect() }))
              .filter(item => item.r.x >= 0 && item.r.x < 260 && item.r.y > 40 && item.r.width > 20 && item.r.height > 10)
              .map(item => item.text)
              .filter(Boolean)"""
        ),
        "frames": [{"url": frame.url} for frame in page.frames],
    }
    write_json(name, state)
    page.screenshot(path=str(OUT_DIR / f"{name}.png"), full_page=True)


def probe_frames(page, name: str) -> None:
    frames = []
    for index, frame in enumerate(page.frames):
        if frame == page.main_frame:
            continue
        frames.append(
            {
                "index": index,
                "url": frame.url,
                "text": safe_text(lambda frame=frame: frame.locator("body").inner_text(timeout=3000)),
                "tables": safe_eval(frame, TABLE_SCRIPT),
                "buttons": safe_eval(frame, BUTTON_SCRIPT),
                "inputs": safe_eval(frame, INPUT_SCRIPT),
                "selects": safe_eval(frame, SELECT_SCRIPT),
            }
        )
    write_json(f"{name}_frames", frames)


def probe_dictionary_pages(frame) -> None:
    snapshots = []
    categories = ["工单-问题类型", "工单-紧急程度", "工单-跟进类型", "售后-申请类型", "申诉类型", "处罚类型"]
    modal_snapshots = []
    for category in categories:
        click_text(frame, [category])
        frame.page.wait_for_timeout(800)
        click_text(frame, ["添加选项"])
        frame.page.wait_for_timeout(800)
        modal_snapshots.append(
            {
                "category": category,
                "text": safe_text(lambda: frame.locator("body").inner_text(timeout=5000)),
                "modal": safe_eval(frame, MODAL_SCRIPT),
                "inputs": safe_eval(frame, INPUT_SCRIPT),
                "selects": safe_eval(frame, SELECT_SCRIPT),
                "buttons": safe_eval(frame, BUTTON_SCRIPT),
            }
        )
        close_modal(frame)
        frame.page.wait_for_timeout(500)
    write_json("dictionary_add_modal_snapshots", modal_snapshots)

    for keyword in ["工单", "售后", "问题", "紧急", "类型", "原因", "凭证"]:
        fill_filter(frame, keyword)
        click_text(frame, ["查询", "查 询", "搜索"])
        frame.page.wait_for_timeout(1200)
        snapshots.append(
            {
                "keyword": keyword,
                "text": safe_text(lambda: frame.locator("body").inner_text(timeout=5000)),
                "tables": safe_eval(frame, TABLE_SCRIPT),
                "buttons": safe_eval(frame, BUTTON_SCRIPT),
                "inputs": safe_eval(frame, INPUT_SCRIPT),
                "selects": safe_eval(frame, SELECT_SCRIPT),
            }
        )
    write_json("dictionary_keyword_snapshots", snapshots)


def click_sidebar(page, text: str, right_edge: bool = False) -> bool:
    boxes = page.evaluate(
        """text => Array.from(document.querySelectorAll('taro-view-core, div, span'))
          .map(el => ({ text: (el.innerText || el.textContent || '').trim(), r: el.getBoundingClientRect() }))
          .filter(item => item.r.x >= 0 && item.r.x < 260 && item.r.y > 40 && item.r.width > 20 && item.r.height > 10)
          .filter(item => item.text === text || item.text.split(/\\s+/).includes(text))
          .sort((a, b) => {
            if (a.text === text && b.text !== text) return -1;
            if (b.text === text && a.text !== text) return 1;
            return a.r.y - b.r.y || (a.r.width * a.r.height) - (b.r.width * b.r.height);
          })
          .map(item => ({ x: item.r.x, y: item.r.y, width: item.r.width, height: item.r.height, text: item.text }))""",
        text,
    )
    if not boxes:
        return False
    box = boxes[0]
    x = box["x"] + box["width"] - 16 if right_edge else box["x"] + box["width"] / 2
    page.mouse.click(x, box["y"] + box["height"] / 2)
    page.wait_for_timeout(1800)
    return True


def frame_with_keywords(page, keywords: list[str]):
    for frame in page.frames:
        if frame == page.main_frame:
            continue
        if "/dataDict" in frame.url:
            return frame
    for frame in page.frames:
        if frame == page.main_frame:
            continue
        text = safe_text(lambda frame=frame: frame.locator("body").inner_text(timeout=2000))
        if any(keyword in text for keyword in keywords):
            return frame
    return None


def fill_filter(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 click_text(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 close_modal(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 cancel = Array.from(root?.querySelectorAll('button') || [])
              .find(el => ['取 消', '取消', '关 闭', '关闭'].includes((el.innerText || el.textContent || '').trim()));
            const close = root?.querySelector('.ant-modal-close');
            (cancel || close)?.click();
        }"""
    )


def safe_text(fn) -> str:
    try:
        return str(fn())
    except Exception as exc:
        return f"<error: {exc}>"


def safe_eval(frame, script: str):
    try:
        return frame.evaluate(script)
    except Exception as exc:
        return {"error": str(exc)}


def write_json(name: str, value) -> None:
    (OUT_DIR / f"{name}.json").write_text(json.dumps(value, ensure_ascii=False, indent=2), encoding="utf-8")


TABLE_SCRIPT = """() => Array.from(document.querySelectorAll('table')).map((table, tableIndex) => ({
  tableIndex,
  headers: Array.from(table.querySelectorAll('th')).map(el => (el.innerText || el.textContent || '').trim()).filter(Boolean),
  rows: Array.from(table.querySelectorAll('tbody tr')).slice(0, 20).map(row =>
    Array.from(row.querySelectorAll('td')).map(el => (el.innerText || el.textContent || '').trim())
  )
}))"""
BUTTON_SCRIPT = """() => Array.from(document.querySelectorAll('button, a')).map(el => ({
  text: (el.innerText || el.textContent || '').trim(),
  r: (() => { const r = el.getBoundingClientRect(); return { x: r.x, y: r.y, width: r.width, height: r.height }; })()
})).filter(item => item.r.width > 0 && item.r.height > 0)"""
INPUT_SCRIPT = """() => Array.from(document.querySelectorAll('input, textarea')).map(el => ({
  placeholder: el.getAttribute('placeholder') || '',
  value: el.value || '',
  type: el.getAttribute('type') || '',
  r: (() => { const r = el.getBoundingClientRect(); return { x: r.x, y: r.y, width: r.width, height: r.height }; })()
})).filter(item => item.r.width > 0 && item.r.height > 0)"""
SELECT_SCRIPT = """() => Array.from(document.querySelectorAll('.ant-select-selector')).map(el => ({
  text: (el.innerText || el.textContent || '').trim(),
  r: (() => { const r = el.getBoundingClientRect(); return { x: r.x, y: r.y, width: r.width, height: r.height }; })()
})).filter(item => item.r.width > 0 && item.r.height > 0)"""
MODAL_SCRIPT = """() => Array.from(document.querySelectorAll('.ant-modal')).map((modal, modalIndex) => {
  const r = modal.getBoundingClientRect();
  return {
    modalIndex,
    visible: r.width > 0 && r.height > 0,
    text: (modal.innerText || modal.textContent || '').trim(),
    labels: Array.from(modal.querySelectorAll('label, .ant-form-item-label')).map(el => (el.innerText || el.textContent || '').trim()).filter(Boolean),
    inputs: Array.from(modal.querySelectorAll('input, textarea')).map(el => ({
      placeholder: el.getAttribute('placeholder') || '',
      value: el.value || '',
      type: el.getAttribute('type') || ''
    })),
    selects: Array.from(modal.querySelectorAll('.ant-select-selector')).map(el => (el.innerText || el.textContent || '').trim()).filter(Boolean),
    buttons: Array.from(modal.querySelectorAll('button')).map(el => (el.innerText || el.textContent || '').trim()).filter(Boolean)
  };
}).filter(item => item.visible)"""


if __name__ == "__main__":
    main()
