from __future__ import annotations

import json
import sys
from pathlib import Path

from dotenv import load_dotenv
from playwright.sync_api import Page, sync_playwright

ROOT_DIR = Path(__file__).resolve().parents[1]
if str(ROOT_DIR) not in sys.path:
    sys.path.insert(0, str(ROOT_DIR))

from e2e.pages.wanyouji_member_page import WANYOUJI_CONSOLE_URL
from e2e.utils.account_store import account_store


TENANT_HOME_URL = "https://test-lianlianyun.lianlianwz.com/home"
OUT_DIR = Path("runtime/member_probe/customer_service_login")


def main() -> None:
    load_dotenv()
    store = account_store()
    account = next(
        (
            item
            for item in reversed(store.load())
            if item.account_type == "sub"
            and item.member_role == "客服"
            and item.member_status in {"invited", "registered", "joined"}
        ),
        None,
    )
    if account is None:
        raise SystemExit("台账中没有可用于探测的客服子账号")

    OUT_DIR.mkdir(parents=True, exist_ok=True)
    with sync_playwright() as playwright:
        browser = playwright.chromium.launch(headless=False, slow_mo=150)
        context = browser.new_context(viewport={"width": 1920, "height": 1080})
        page = context.new_page()
        login_password_account(page, account.phone, account.password)
        dump_page(page, "01_登录后")

        open_wanyouji_console(page)
        dump_page(page, "02_丸友集控制台")

        probe_menus(page, ["收单应用", "客户自助填单", "打手订单", "工作室履约应用", "订单列表"])
        dump_page(page, "03_菜单探测后")

        page.wait_for_timeout(5000)
        context.close()
        browser.close()

    print(f"客服子账号登录探测完成：{account.phone}")
    print(f"探测结果目录：{OUT_DIR.resolve()}")


def login_password_account(page: Page, phone: str, password: str) -> None:
    page.context.clear_cookies()
    page.goto(TENANT_HOME_URL.replace("/home", "/login"), wait_until="domcontentloaded")
    page.wait_for_selector("#password_login_phone", state="visible", timeout=15000)
    page.locator("#password_login_phone").fill(phone)
    page.locator("#password_login_password").fill(password)
    page.get_by_text("登 录", exact=True).click()
    page.wait_for_load_state("domcontentloaded")
    page.wait_for_timeout(2500)
    select_sub_account_if_present(page, phone)


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

    clicked = page.evaluate(
        """phone => {
            const cards = Array.from(document.querySelectorAll('taro-view-core, div'))
              .map(el => ({ el, text: el.innerText || el.textContent || '', r: el.getBoundingClientRect() }))
              .filter(item => item.r.width > 260 && item.r.height > 45)
              .filter(item => item.text.includes('子账号') || item.text.includes('客服') || item.text.includes(phone))
              .sort((a, b) => (a.r.width * a.r.height) - (b.r.width * b.r.height));
            const target = cards[0]?.el;
            if (!target) return false;
            target.click();
            return true;
        }""",
        phone,
    )
    if not clicked:
        raise AssertionError(f"登录后未找到客服子账号卡片；text={body_text[:1200]}")
    page.wait_for_load_state("domcontentloaded")
    page.wait_for_timeout(3000)


def open_wanyouji_console(page: Page) -> None:
    page.goto(WANYOUJI_CONSOLE_URL, wait_until="domcontentloaded")
    for _ in range(8):
        text = page.locator("body").inner_text(timeout=3000)
        if any(key in text for key in ["工作室", "收单应用", "工作室基础应用", "进入后台", "客服"]):
            return
        page.wait_for_timeout(1000)
    raise AssertionError(f"客服子账号未打开丸友集控制台；text={page.locator('body').inner_text(timeout=5000)[:1200]}")


def probe_menus(page: Page, menus: list[str]) -> None:
    for menu in menus:
        clicked = click_visible_text(page, menu)
        page.wait_for_timeout(2000)
        dump_page(page, f"菜单_{menu}_{'已点击' if clicked else '未找到'}")


def click_visible_text(page: Page, text: str) -> bool:
    return bool(
        page.evaluate(
            """text => {
                const nodes = Array.from(document.querySelectorAll('taro-view-core, div, span, button, a'));
                const target = 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 === 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.width * a.r.height) - (b.r.width * b.r.height);
                  })[0];
                if (!target) return false;
                target.el.click();
                return true;
            }""",
            text,
        )
    )


def dump_page(page: Page, name: str) -> None:
    payload = {
        "url": page.url,
        "text": safe_page_text(page)[:3000],
        "frames": [
            {
                "url": frame.url,
                "text": safe_frame_text(frame)[:2500],
                "nodes": safe_frame_nodes(frame),
            }
            for frame in page.frames
            if frame.url != "about:blank"
        ],
        "nodes": safe_page_nodes(page),
    }
    (OUT_DIR / f"{name}.json").write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
    try:
        page.screenshot(path=OUT_DIR / f"{name}.png", full_page=True)
    except Exception:
        pass


def safe_page_text(page: Page) -> str:
    try:
        return page.locator("body").inner_text(timeout=5000)
    except Exception as exc:
        return f"<无法读取页面文本：{exc}>"


def safe_frame_text(frame) -> str:
    try:
        return frame.locator("body").inner_text(timeout=3000)
    except Exception as exc:
        return f"<无法读取 iframe 文本：{exc}>"


def safe_page_nodes(page: Page) -> list[dict]:
    try:
        return list(page.evaluate(VISIBLE_NODES_SCRIPT))
    except Exception:
        return []


def safe_frame_nodes(frame) -> list[dict]:
    try:
        return list(frame.evaluate(VISIBLE_NODES_SCRIPT))
    except Exception:
        return []


VISIBLE_NODES_SCRIPT = """() => Array.from(document.querySelectorAll('button, a, span, div, input, textarea, .ant-select-selector'))
  .map((el, index) => {
    const r = el.getBoundingClientRect();
    return {
      index,
      tag: el.tagName,
      text: (el.innerText || el.textContent || '').trim(),
      placeholder: el.getAttribute?.('placeholder') || '',
      className: String(el.className || ''),
      rect: { x: r.x, y: r.y, width: r.width, height: r.height },
    };
  })
  .filter(item => item.rect.width > 0 && item.rect.height > 0 && (item.text || item.placeholder))
  .slice(0, 500)"""


if __name__ == "__main__":
    main()
