from __future__ import annotations

import json
import os
import sys
from datetime import datetime
from pathlib import Path

from dotenv import load_dotenv
from playwright.sync_api import sync_playwright

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

from e2e.conftest import settings as settings_fixture
from e2e.pages.wanzhanggui_order_page import WanyoujiFulfillmentPage
from e2e.tests.订单流转.test_e2e_task_001_external_order_dispatch_to_player import (
    login_player_sub_account_by_sms,
)
from e2e.utils.account_store import account_store


OUT_DIR = ROOT / "runtime" / "probes" / "player_my_orders_source_search"


def main() -> None:
    if hasattr(sys.stdout, "reconfigure"):
        sys.stdout.reconfigure(encoding="utf-8")
    load_dotenv(ROOT / ".env")
    settings = settings_fixture.__wrapped__()
    source_order_no = os.getenv("E2E_S_FLOW_SOURCE_ORDER_NO", "E2EORDER0701103514").strip()
    player_phone = os.getenv("E2E_TASK_PLAYER_PHONE", "19873945913").strip()

    store = account_store()
    player = store.find(phone=player_phone)
    if player is None:
        raise SystemExit(f"台账中未找到打手账号：{player_phone}")

    OUT_DIR.mkdir(parents=True, exist_ok=True)
    run_dir = OUT_DIR / f"{datetime.now().strftime('%Y%m%d-%H%M%S')}-{player_phone}-{source_order_no}"
    run_dir.mkdir(parents=True, exist_ok=True)

    with sync_playwright() as playwright:
        browser = playwright.chromium.launch(headless=False, slow_mo=120)
        context = browser.new_context(viewport={"width": settings.viewport_width, "height": settings.viewport_height})
        page = context.new_page()
        try:
            player_page_raw = login_player_sub_account_by_sms(page, settings, None, player)
            fulfillment = WanyoujiFulfillmentPage(player_page_raw, settings.tenant_home_url)
            frame = fulfillment.open_player_my_orders(source_order_no=source_order_no)
            result = {
                "source_order_no": source_order_no,
                "player_phone": player_phone,
                "page_url": player_page_raw.url,
                "frame_url": frame.url,
                "initial_inputs": describe_inputs(frame),
                "attempts": [],
            }
            tabs = ["全部", "代练中", "待验收", "已完成", "撤销中", "撤销完成"]
            labels = ["来源单号", "订单号", "主单号", "渠道单号", "打手单号"]
            for tab in tabs:
                click_text(frame, tab)
                player_page_raw.wait_for_timeout(800)
                for label in labels:
                    reset(frame)
                    click_text(frame, tab)
                    player_page_raw.wait_for_timeout(500)
                    fill_result = fill_by_label(frame, label, source_order_no)
                    if not fill_result.get("ok"):
                        continue
                    click_text(frame, "查 询") or click_text(frame, "查询")
                    player_page_raw.wait_for_timeout(2000)
                    text = read_text(frame)
                    result["attempts"].append(
                        {
                            "tab": tab,
                            "label": label,
                            "fill_result": fill_result,
                            "found": source_order_no in text,
                            "text": text[:2500],
                        }
                    )
                    if source_order_no in text:
                        player_page_raw.screenshot(path=run_dir / f"found-{tab}-{label}.png", full_page=True)
            result["final_inputs"] = describe_inputs(frame)
            result["page_text"] = read_text(page)[:3000]
            result["frame_text"] = read_text(frame)[:5000]
            (run_dir / "result.json").write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
            player_page_raw.screenshot(path=run_dir / "final.png", full_page=True)
            print(json.dumps(result, ensure_ascii=False, indent=2)[:12000])
            print(f"probe_dir={run_dir}")
        finally:
            context.close()
            browser.close()


def read_text(scope) -> str:
    try:
        return scope.locator("body").inner_text(timeout=5000)
    except Exception as exc:
        return f"<read failed: {exc}>"


def reset(frame) -> None:
    click_text(frame, "重 置") or click_text(frame, "重置")
    frame.page.wait_for_timeout(700)


def click_text(frame, text: str) -> bool:
    return bool(
        frame.evaluate(
            """text => {
                const nodes = Array.from(document.querySelectorAll('button, span, div, a, .ant-tabs-tab, .ant-tabs-tab-btn'));
                const candidates = nodes
                  .map(el => ({ el, text: (el.innerText || el.textContent || '').trim(), r: el.getBoundingClientRect(), disabled: el.disabled }))
                  .filter(item => item.r.width > 0 && item.r.height > 0 && !item.disabled)
                  .filter(item => item.text === text || item.text.replace(/\\s+/g, '') === text.replace(/\\s+/g, ''))
                  .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);
                  });
                const target = candidates[0];
                if (!target) return false;
                target.el.click();
                return true;
            }""",
            text,
        )
    )


def describe_inputs(frame) -> list[dict]:
    return frame.evaluate(
        """() => {
            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 => ({ text: (el.innerText || el.textContent || '').trim(), r: el.getBoundingClientRect() }))
              .filter(item => item.r.width > 0 && item.r.height > 0 && item.r.y < 260);
            return Array.from(document.querySelectorAll('input'))
              .map((el, index) => {
                const r = el.getBoundingClientRect();
                const near = labels
                  .filter(label => Math.abs(label.r.y - r.y) < 40 && label.r.x < r.x)
                  .sort((a, b) => Math.abs(a.r.y - r.y) - Math.abs(b.r.y - r.y) || b.r.x - a.r.x)[0];
                return {
                  index,
                  value: el.value || '',
                  placeholder: el.getAttribute('placeholder') || '',
                  type: el.getAttribute('type') || '',
                  disabled: Boolean(el.disabled),
                  rect: { x: r.x, y: r.y, width: r.width, height: r.height },
                  nearLabel: near?.text || '',
                  visible: visible(el),
                };
              })
              .filter(item => item.visible);
        }"""
    )


def fill_by_label(frame, label_text: str, value: str) -> dict:
    return frame.evaluate(
        """({ labelText, value }) => {
            const visible = el => {
              const r = el.getBoundingClientRect();
              return r.width > 0 && r.height > 0;
            };
            const norm = text => String(text || '').replace(/\\s+/g, '');
            const setValue = input => {
              input.focus();
              const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
              setter.call(input, value);
              input.dispatchEvent(new InputEvent('input', { bubbles: true, inputType: 'insertText', data: value }));
              input.dispatchEvent(new Event('change', { bubbles: true }));
              input.dispatchEvent(new FocusEvent('blur', { bubbles: true }));
              return { ok: true, value: input.value, placeholder: input.getAttribute('placeholder') || '' };
            };
            const placeholderInput = Array.from(document.querySelectorAll('input'))
              .find(input => {
                const placeholder = input.getAttribute('placeholder') || '';
                return visible(input) &&
                  !input.disabled &&
                  input.getAttribute('type') !== 'hidden' &&
                  norm(placeholder).includes(norm(labelText));
              });
            if (placeholderInput) {
              return { ...setValue(placeholderInput), label: labelText, method: 'placeholder' };
            }
            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))
              .filter(item => norm(item.text) === norm(labelText) || norm(item.text) === norm(labelText + '：') || norm(item.text) === norm(labelText + ':'))
              .sort((a, b) => a.r.y - b.r.y || a.r.x - b.r.x);
            for (const label of labels) {
              const container = label.el.closest('.ant-form-item, [class*="form-item"], .ant-row, [class*="filter"], [class*="search"]');
              const containedInput = container
                ? Array.from(container.querySelectorAll('input')).find(input => visible(input) && !input.disabled && input.getAttribute('type') !== 'hidden')
                : null;
              if (containedInput) {
                return { ...setValue(containedInput), label: label.text, method: 'container' };
              }
              const inputs = Array.from(document.querySelectorAll('input'))
                .map(el => ({ el, r: el.getBoundingClientRect(), placeholder: el.getAttribute('placeholder') || '' }))
                .filter(item => visible(item.el) && !item.el.disabled && item.el.getAttribute('type') !== 'hidden')
                .filter(item => Math.abs(item.r.y - label.r.y) < 44 && item.r.x > label.r.x)
                .sort((a, b) => a.r.x - b.r.x);
              if (inputs[0]) {
                return { ...setValue(inputs[0].el), label: label.text, method: 'same-row' };
              }
            }
            return { ok: false, reason: 'label not found', labelText };
        }""",
        {"labelText": label_text, "value": value},
    )


if __name__ == "__main__":
    main()
