from __future__ import annotations

import os
from datetime import datetime
from pathlib import Path

from playwright.sync_api import Frame, Page, sync_playwright


租户首页 = "https://test-lianlianyun.lianlianwz.com/home"
丸友集控制台 = (
    "https://test-merchant-lianlianyun.lianlianwz.com/"
    "productConsole?productCode=P20250814095939369041"
)

账号 = os.getenv("E2E_WANYOUJI_PHONE", "19038951373")
密码 = os.getenv("E2E_WANYOUJI_PASSWORD", "test123456")
来源单号 = os.getenv("E2E_ORDER_SOURCE_NO", "E2EORDER0604181415")

输出目录 = Path("runtime/order_probe/find_existing_order")


def 保存现场(page: Page, name: str) -> None:
    输出目录.mkdir(parents=True, exist_ok=True)
    timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
    safe_name = f"{timestamp}-{name}"
    page.screenshot(path=输出目录 / f"{safe_name}.png", full_page=True)
    (输出目录 / f"{safe_name}-frames.txt").write_text(
        "\n\n".join(
            f"===== FRAME {index} =====\nURL: {frame.url}\n\n{读取文本(frame)}"
            for index, frame in enumerate(page.frames)
        ),
        encoding="utf-8",
    )


def 读取文本(frame: Frame) -> str:
    try:
        return frame.locator("body").inner_text(timeout=3000)
    except Exception as exc:
        return f"<读取失败: {exc}>"


def 点击可见文本(page: Page, text: str, right_edge: bool = False) -> bool:
    clicked = page.evaluate(
        """({ text, rightEdge }) => {
            const nodes = Array.from(document.querySelectorAll('taro-view-core, div, span, button, a'));
            const candidates = nodes
              .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.split(/\\s+/).includes(text))
              .sort((a, b) => {
                if (a.value === text && b.value !== text) return -1;
                if (b.value === text && a.value !== text) return 1;
                return (a.r.width * a.r.height) - (b.r.width * b.r.height);
              });
            const target = candidates[0];
            if (!target) return false;
            const x = rightEdge ? target.r.x + target.r.width - 16 : target.r.x + target.r.width / 2;
            const y = target.r.y + target.r.height / 2;
            target.el.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, clientX: x, clientY: y }));
            target.el.click();
            return true;
        }""",
        {"text": text, "rightEdge": right_edge},
    )
    page.wait_for_timeout(1000)
    return bool(clicked)


def 点击框架文本(frame: Frame, text: str) -> bool:
    clicked = frame.evaluate(
        """text => {
            const nodes = Array.from(document.querySelectorAll('.ant-tabs-tab, .ant-tabs-tab-btn, button, span, div, a'));
            const candidates = nodes
              .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) => {
                if (a.value === text && b.value !== text) return -1;
                if (b.value === text && a.value !== text) return 1;
                return (a.r.width * a.r.height) - (b.r.width * b.r.height);
              });
            const target = candidates[0];
            if (!target) return false;
            target.el.click();
            return true;
        }""",
        text,
    )
    return bool(clicked)


def 登录主账号(page: Page) -> None:
    page.context.clear_cookies()
    page.goto(租户首页.replace("/home", "/login"), wait_until="domcontentloaded")
    page.wait_for_selector("#password_login_phone", state="visible", timeout=15000)
    page.locator("#password_login_phone").fill(账号)
    page.locator("#password_login_password").fill(密码)

    clicked = page.evaluate(
        """() => {
            const buttons = Array.from(document.querySelectorAll('button, taro-button-core, div'));
            const target = buttons.find(el => {
              const text = (el.innerText || el.textContent || '').trim();
              const r = el.getBoundingClientRect();
              return r.width > 0 && r.height > 0 && text === '登录';
            }) || document.querySelector('button[type="submit"]');
            if (!target) return false;
            target.click();
            return true;
        }"""
    )
    if not clicked:
        page.locator("#password_login_password").press("Enter")
    page.wait_for_load_state("domcontentloaded")
    page.wait_for_timeout(2500)

    body_text = page.locator("body").inner_text(timeout=8000)
    if "选择账号" not in body_text and "/account/select" not in page.url:
        return

    selected = page.evaluate(
        """() => {
            const cards = Array.from(document.querySelectorAll('taro-view-core, div'))
              .map(el => ({ el, text: el.innerText || el.textContent || '', r: el.getBoundingClientRect() }))
              .filter(item => item.text.includes('主账号') && !item.text.includes('子账号'))
              .filter(item => item.r.width > 200 && item.r.height > 40)
              .sort((a, b) => (a.r.width * a.r.height) - (b.r.width * b.r.height));
            const target = cards[0];
            if (!target) return false;
            target.el.click();
            return true;
        }"""
    )
    if not selected:
        raise AssertionError(f"登录后没有找到主账号入口，当前页面文本：{body_text[:1000]}")
    page.wait_for_load_state("domcontentloaded")
    page.wait_for_timeout(2500)


def 打开抢单大厅(page: Page) -> list[Frame]:
    page.goto(丸友集控制台, wait_until="domcontentloaded")
    page.wait_for_timeout(4000)
    点击可见文本(page, "工作室履约应用", right_edge=True)
    点击可见文本(page, "抢单大厅")
    page.wait_for_timeout(4000)
    frames = [
        frame
        for frame in page.frames
        if ("test-wshop" in frame.url or "test-orbit" in frame.url) and "order" in frame.url.lower()
    ]
    if not frames:
        保存现场(page, "未找到抢单大厅iframe")
        raise AssertionError(f"未找到抢单大厅 iframe，当前 frames：{[frame.url for frame in page.frames]}")
    return frames


def 填写单号并查询(frame: Frame, keyword: str) -> dict:
    result = frame.evaluate(
        """keyword => {
            const visible = el => {
              const r = el.getBoundingClientRect();
              return r.width > 0 && r.height > 0;
            };
            const labels = Array.from(document.querySelectorAll('label, div, span'))
              .map(el => ({ el, text: (el.innerText || el.textContent || '').trim(), r: el.getBoundingClientRect() }))
              .filter(item => visible(item.el));
            const wanted = ['来源单号', '上游单号', '订单编号', '订单标题'];
            let input = null;
            let labelText = '';
            for (const labelName of wanted) {
              const label = labels.find(item => item.text === labelName || item.text.includes(labelName));
              if (!label) continue;
              const inputs = Array.from(document.querySelectorAll('input'))
                .map(el => ({ el, placeholder: el.getAttribute('placeholder') || '', r: el.getBoundingClientRect() }))
                .filter(item => visible(item.el))
                .filter(item => Math.abs(item.r.y - label.r.y) < 35 && item.r.x > label.r.x - 5)
                .sort((a, b) => a.r.x - b.r.x);
              if (inputs[0]) {
                input = inputs[0].el;
                labelText = labelName;
                break;
              }
            }
            if (!input) {
              const inputs = Array.from(document.querySelectorAll('input'))
                .map(el => ({ el, placeholder: el.getAttribute('placeholder') || '', r: el.getBoundingClientRect() }))
                .filter(item => visible(item.el))
                .filter(item => /订单|单号|搜索|来源|上游/.test(item.placeholder));
              input = inputs[0]?.el || null;
              labelText = inputs[0]?.placeholder || '<first-match>';
            }
            if (!input) return { ok: false, reason: '未找到可输入订单号的搜索框' };
            input.focus();
            const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
            setter.call(input, keyword);
            input.dispatchEvent(new InputEvent('input', { bubbles: true, inputType: 'insertText', data: keyword }));
            input.dispatchEvent(new Event('change', { bubbles: true }));
            input.dispatchEvent(new FocusEvent('blur', { bubbles: true }));
            return { ok: true, field: labelText, value: input.value };
        }""",
        keyword,
    )
    if not result.get("ok"):
        return result

    点击框架文本(frame, "查询") or 点击框架文本(frame, "搜索")
    frame.page.wait_for_timeout(2500)
    return result


def 在抢单大厅查单(page: Page, frames: list[Frame]) -> bool:
    命中摘要: list[str] = []
    for tab in ["私单", "公池单"]:
        for frame in frames:
            点击框架文本(frame, tab)
            page.wait_for_timeout(1000)
            result = 填写单号并查询(frame, 来源单号)
            text = 读取文本(frame)
            命中摘要.append(f"[{tab}] {frame.url}\n查询字段：{result}\n{text[:1500]}")
            if 来源单号 in text:
                print(f"已在丸友集抢单大厅-{tab} 找到订单：{来源单号}")
                保存现场(page, f"找到订单-{tab}-{来源单号}")
                return True
    (输出目录 / "last-search-summary.txt").write_text("\n\n".join(命中摘要), encoding="utf-8")
    return False


def 在订单列表查单(page: Page) -> bool:
    点击可见文本(page, "订单列表")
    page.wait_for_timeout(3500)
    frames = [
        frame
        for frame in page.frames
        if ("test-wshop" in frame.url or "test-orbit" in frame.url) and "order" in frame.url.lower()
    ]
    for frame in frames:
        result = 填写单号并查询(frame, 来源单号)
        text = 读取文本(frame)
        if 来源单号 in text:
            print(f"已在丸友集订单列表找到订单：{来源单号}，查询字段：{result}")
            保存现场(page, f"订单列表找到订单-{来源单号}")
            return True
    return False


def main() -> None:
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False, slow_mo=120)
        context = browser.new_context(viewport={"width": 1920, "height": 1080})
        page = context.new_page()
        try:
            登录主账号(page)
            frames = 打开抢单大厅(page)
            if 在抢单大厅查单(page, frames):
                return
            if 在订单列表查单(page):
                return
            保存现场(page, f"未找到订单-{来源单号}")
            raise AssertionError(f"丸友集账号 {账号} 未找到订单 {来源单号}，已保存现场到 {输出目录}")
        finally:
            page.wait_for_timeout(3000)
            context.close()
            browser.close()


if __name__ == "__main__":
    main()
