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]
sys.path.insert(0, str(ROOT))

from e2e.conftest import E2ESettings
from e2e.pages.wanzhanggui_order_page import WanyoujiFulfillmentPage
from e2e.utils.account_store import E2EAccount, account_store


load_dotenv()


def settings_from_env() -> E2ESettings:
    import os

    return E2ESettings(
        tenant_base_url=os.getenv("TENANT_BASE_URL", "https://test-lianlianyun.lianlianwz.com"),
        tenant_home_url=os.getenv("TENANT_HOME_URL", "https://test-lianlianyun.lianlianwz.com/home"),
        admin_url=os.getenv("ADMIN_URL", "https://test-saas-adminui.lianlianwz.com/pages/home/index"),
        admin_username=os.getenv("ADMIN_USERNAME", "wangss"),
        admin_password=os.getenv("ADMIN_PASSWORD", "123456"),
        register_password=os.getenv("E2E_REGISTER_PASSWORD", "test123456"),
        phone_prefix=os.getenv("E2E_PHONE_PREFIX", "19"),
        max_register_attempts=int(os.getenv("E2E_MAX_REGISTER_ATTEMPTS", "5")),
        enterprise_name_prefix=os.getenv("E2E_ENTERPRISE_NAME_PREFIX", "E2E测试企业"),
        verification_code_mode=os.getenv("E2E_VERIFICATION_CODE_MODE", "feishu"),
        verification_code_static=os.getenv("E2E_VERIFICATION_CODE_STATIC", ""),
        manual_code_file=os.getenv("E2E_MANUAL_CODE_FILE", "runtime/sms_code.txt"),
        manual_code_timeout_seconds=int(os.getenv("E2E_MANUAL_CODE_TIMEOUT_SECONDS", "180")),
        manual_code_poll_interval_seconds=int(os.getenv("E2E_MANUAL_CODE_POLL_INTERVAL_SECONDS", "2")),
        image_captcha_mode=os.getenv("E2E_IMAGE_CAPTCHA_MODE", "ddddocr"),
        feishu_app_id=os.getenv("FEISHU_APP_ID", ""),
        feishu_app_secret=os.getenv("FEISHU_APP_SECRET", ""),
        feishu_chat_id=os.getenv("FEISHU_CHAT_ID", ""),
        feishu_timeout_seconds=int(os.getenv("FEISHU_TIMEOUT_SECONDS", "60")),
        feishu_poll_interval_seconds=int(os.getenv("FEISHU_POLL_INTERVAL_SECONDS", "3")),
        certified_tenant_phone=os.getenv("E2E_CERTIFIED_TENANT_PHONE", "19361091624"),
        certified_tenant_password=os.getenv("E2E_CERTIFIED_TENANT_PASSWORD", "test123456"),
        certified_tenant_enterprise_name=os.getenv("E2E_CERTIFIED_TENANT_ENTERPRISE_NAME", "E2E测试企业0603174618802"),
        certified_tenant_merchant_id=os.getenv("E2E_CERTIFIED_TENANT_MERCHANT_ID", "99"),
        wanyouji_product_id=os.getenv("E2E_WANYOUJI_PRODUCT_ID", "12"),
        wanzhanggui_product_id=os.getenv("E2E_WANZHANGGUI_PRODUCT_ID", "15"),
        wanzhanggui_tenant_phone=os.getenv("E2E_WANZHANGGUI_TENANT_PHONE", "19147509308"),
        wanzhanggui_tenant_password=os.getenv("E2E_WANZHANGGUI_TENANT_PASSWORD", "test123456"),
        viewport_width=int(os.getenv("E2E_VIEWPORT_WIDTH", "1920")),
        viewport_height=int(os.getenv("E2E_VIEWPORT_HEIGHT", "1080")),
    )


def resolve_wanyouji_owner() -> tuple[E2EAccount, dict]:
    store = account_store()
    payload = store.load_payload()
    orders = sorted(
        payload.get("dispatched_orders", []),
        key=lambda item: item.get("updated_at", item.get("created_at", "")),
        reverse=True,
    )
    preferred_status = {
        "settlement_bill_submitted",
        "player_settlement_bill_submitted",
        "income_verified",
        "delivery_accepted",
        "delivery_submitted",
    }
    for order in orders:
        if order.get("status") not in preferred_status:
            continue
        for phone in [order.get("wanyouji_phone"), *(order.get("wanyouji_phones") or [])]:
            if not phone:
                continue
            account = next((item for item in store.load() if item.phone == phone), None)
            if account:
                return account, order

    candidates = [
        account
        for account in store.load()
        if account.account_type == "main"
        and account.wanyouji_status == "opened"
        and account.certification_status == "approved"
    ]
    candidates = sorted(candidates, key=lambda item: item.updated_at, reverse=True)
    if not candidates:
        raise AssertionError("台账中未找到可登录的丸友集主账号。")
    return candidates[0], {}


def visible_buttons_and_fields(frame) -> dict:
    return frame.evaluate(
        """() => {
            const visible = el => {
              const r = el.getBoundingClientRect();
              return r.width > 0 && r.height > 0;
            };
            const buttons = Array.from(document.querySelectorAll('button, a, [role="button"], span'))
              .filter(visible)
              .map(el => (el.innerText || el.textContent || '').trim())
              .filter(Boolean)
              .slice(0, 200);
            const fields = Array.from(document.querySelectorAll('input, textarea, .ant-select-selector'))
              .filter(visible)
              .map(el => {
                const item = el.closest('.ant-form-item, label, div');
                return {
                  placeholder: el.getAttribute('placeholder') || '',
                  text: (item?.innerText || el.innerText || el.textContent || '').trim().slice(0, 120)
                };
              })
              .slice(0, 100);
            const modals = Array.from(document.querySelectorAll('.ant-modal, .ant-drawer'))
              .filter(visible)
              .map(el => (el.innerText || el.textContent || '').trim().slice(0, 3000));
            return { buttons, fields, modals };
        }"""
    )


def click_text(frame, text: str) -> bool:
    clicked = frame.evaluate(
        """text => {
            const nodes = Array.from(document.querySelectorAll('button, a, [role="button"], span, div'))
              .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) || item.text.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);
              });
            const target = nodes[0];
            if (!target) return false;
            target.el.scrollIntoView({ block: 'center', inline: 'center' });
            target.el.click();
            return true;
        }""",
        text,
    )
    return bool(clicked)


def visible_dropdown_options_anywhere(frame) -> list[str]:
    options: list[str] = []
    for root in [frame, frame.page]:
        try:
            options.extend(
                root.evaluate(
                    """() => Array.from(document.querySelectorAll(
                      '.ant-select-dropdown:not(.ant-select-dropdown-hidden) .ant-select-item-option, .ant-select-item-option'
                    )).map(el => {
                      const r = el.getBoundingClientRect();
                      return { text: (el.innerText || el.textContent || '').trim(), visible: r.width > 0 && r.height > 0 };
                    }).filter(item => item.visible && item.text).map(item => item.text)"""
                )
            )
        except Exception:
            continue
    return options


def open_modal_select_dropdown(frame, label_keywords: list[str], fallback_index: int | None = None) -> dict:
    debug: dict = {"labels": label_keywords, "opened": False, "options": [], "fallback_index": fallback_index}
    opened = frame.evaluate(
        """({ labels, fallbackIndex }) => {
            const visible = el => {
              const r = el.getBoundingClientRect();
              return r.width > 0 && r.height > 0;
            };
            const roots = Array.from(document.querySelectorAll('.ant-modal, .ant-drawer')).filter(visible);
            const root = roots[roots.length - 1] || document;
            const allSelectors = Array.from(root.querySelectorAll('.ant-select-selector'))
              .map(el => ({
                el,
                text: (el.innerText || el.textContent || '').trim(),
                itemText: (el.closest('.ant-form-item')?.innerText || '').trim(),
                r: el.getBoundingClientRect()
              }))
              .filter(item => item.r.width > 0 && item.r.height > 0);
            const selectors = allSelectors.filter(item =>
              labels.some(label => item.text.includes(label) || item.itemText.includes(label))
            );
            let target = selectors[0]?.el;
            if (!target && Number.isInteger(fallbackIndex)) {
              target = allSelectors[fallbackIndex]?.el;
            }
            if (!target) return false;
            target.scrollIntoView({ block: 'center', inline: 'center' });
            const r = target.getBoundingClientRect();
            target.dispatchEvent(new MouseEvent('mouseover', { bubbles: true, clientX: r.right - 14, clientY: r.y + r.height / 2 }));
            target.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, clientX: r.right - 14, clientY: r.y + r.height / 2 }));
            target.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, clientX: r.right - 14, clientY: r.y + r.height / 2 }));
            target.dispatchEvent(new MouseEvent('click', { bubbles: true, clientX: r.right - 14, clientY: r.y + r.height / 2 }));
            return true;
        }""",
        {"labels": label_keywords, "fallbackIndex": fallback_index},
    )
    debug["opened"] = bool(opened)
    frame.page.wait_for_timeout(800)
    debug["options"] = visible_dropdown_options_anywhere(frame)
    if not debug["options"]:
        clicked_by_mouse = click_modal_select_arrow_by_mouse(frame, label_keywords, fallback_index)
        debug["mouse_arrow_clicked"] = clicked_by_mouse
        frame.page.wait_for_timeout(800)
        debug["options_after_mouse"] = visible_dropdown_options_anywhere(frame)
        debug["options"] = debug["options_after_mouse"]
    return debug


def select_first_open_dropdown_option(frame) -> str:
    selected = ""
    for root in [frame, frame.page]:
        try:
            selected = root.evaluate(
                """() => {
                    const options = Array.from(document.querySelectorAll(
                      '.ant-select-dropdown:not(.ant-select-dropdown-hidden) .ant-select-item-option:not(.ant-select-item-option-disabled), .ant-select-item-option:not(.ant-select-item-option-disabled)'
                    )).map(el => ({
                      el,
                      text: (el.innerText || el.textContent || '').trim(),
                      r: el.getBoundingClientRect()
                    })).filter(item => item.r.width > 0 && item.r.height > 0 && item.text);
                    const target = options[0];
                    if (!target) return '';
                    target.el.click();
                    return target.text;
                }"""
            )
        except Exception:
            selected = ""
        if selected:
            break
    if not selected:
        try:
            locator = frame.locator(
                ".ant-select-dropdown:not(.ant-select-dropdown-hidden) .ant-select-item-option:not(.ant-select-item-option-disabled)"
            ).first
            selected = locator.inner_text(timeout=1200).strip()
            locator.click(force=True)
        except Exception:
            pass
    if not selected:
        try:
            locator = frame.page.locator(
                ".ant-select-dropdown:not(.ant-select-dropdown-hidden) .ant-select-item-option:not(.ant-select-item-option-disabled)"
            ).first
            selected = locator.inner_text(timeout=1200).strip()
            locator.click(force=True)
        except Exception:
            pass
    frame.page.wait_for_timeout(1200)
    return str(selected or "")


def select_first_modal_option(frame, label_keywords: list[str], fallback_index: int | None = None) -> tuple[str, dict]:
    debug = open_modal_select_dropdown(frame, label_keywords, fallback_index)
    if not debug["opened"] and not debug.get("mouse_arrow_clicked"):
        return "", debug
    if not debug["options"]:
        return "", debug

    selected = select_first_open_dropdown_option(frame)
    return str(selected or ""), debug


def click_modal_select_arrow_by_mouse(frame, label_keywords: list[str], fallback_index: int | None = None) -> bool:
    box = frame.evaluate(
        """({ labels, fallbackIndex }) => {
            const visible = el => {
              const r = el.getBoundingClientRect();
              return r.width > 0 && r.height > 0;
            };
            const roots = Array.from(document.querySelectorAll('.ant-modal, .ant-drawer')).filter(visible);
            const root = roots[roots.length - 1] || document;
            const allSelectors = Array.from(root.querySelectorAll('.ant-select-selector'))
              .map((el, index) => ({
                el,
                index,
                text: (el.innerText || el.textContent || '').trim(),
                itemText: (el.closest('.ant-form-item')?.innerText || '').trim(),
                r: el.getBoundingClientRect()
              }))
              .filter(item => item.r.width > 0 && item.r.height > 0);
            const selectors = allSelectors.filter(item =>
              labels.some(label => item.text.includes(label) || item.itemText.includes(label))
            );
            let target = selectors[0] || (Number.isInteger(fallbackIndex) ? allSelectors[fallbackIndex] : null);
            if (!target) return null;
            target.el.scrollIntoView({ block: 'center', inline: 'center' });
            const r = target.el.getBoundingClientRect();
            return { x: r.right - 14, y: r.y + r.height / 2, text: target.text, itemText: target.itemText };
        }""",
        {"labels": label_keywords, "fallbackIndex": fallback_index},
    )
    if not box:
        return False
    frame_box = frame.frame_element().bounding_box()
    if not frame_box:
        return False
    frame.page.mouse.click(frame_box["x"] + box["x"], frame_box["y"] + box["y"])
    return True


def select_first_modal_option_legacy(frame, label_keywords: list[str]) -> str:
    selected, _ = select_first_modal_option(frame, label_keywords)
    return selected


def dump_modal_selects(frame) -> list[dict]:
    return frame.evaluate(
        """() => {
            const visible = el => {
              const r = el.getBoundingClientRect();
              return r.width > 0 && r.height > 0;
            };
            const roots = Array.from(document.querySelectorAll('.ant-modal, .ant-drawer')).filter(visible);
            const root = roots[roots.length - 1] || document;
            return Array.from(root.querySelectorAll('.ant-select-selector')).map((el, index) => ({
              index,
              text: (el.innerText || el.textContent || '').trim(),
              itemText: (el.closest('.ant-form-item')?.innerText || '').trim(),
              rect: (() => { const r = el.getBoundingClientRect(); return { x: r.x, y: r.y, width: r.width, height: r.height }; })()
            })).filter(item => item.rect.width > 0 && item.rect.height > 0);
        }"""
    )


def click_first_dropdown_option_by_mouse(frame) -> str:
    options = frame.evaluate(
        """() => Array.from(document.querySelectorAll(
          '.ant-select-dropdown:not(.ant-select-dropdown-hidden) .ant-select-item-option:not(.ant-select-item-option-disabled), .ant-select-item-option:not(.ant-select-item-option-disabled)'
        )).map(el => {
            const r = el.getBoundingClientRect();
            return {
              el,
              text: (el.innerText || el.textContent || '').trim(),
              x: r.x + r.width / 2,
              y: r.y + r.height / 2,
              visible: r.width > 0 && r.height > 0
            };
        }).filter(item => item.visible && item.text)"""
    )
    if not options:
        return ""
    box = options[0]
    frame_element = frame.frame_element()
    frame_box = frame_element.bounding_box()
    if frame_box:
        frame.page.mouse.click(frame_box["x"] + box["x"], frame_box["y"] + box["y"])
        frame.page.wait_for_timeout(1200)
        return str(box["text"])
    return ""


def save_online_payment_config_if_possible(frame) -> dict:
    result: dict[str, str | bool] = {
        "selected_app": "",
        "selected_channel": "",
        "clicked_confirm": False,
        "final_text": "",
        "selects_before": [],
        "app_debug": {},
        "channel_debug": {},
    }
    result["selects_before"] = dump_modal_selects(frame)
    app_debug = open_modal_select_dropdown(frame, ["交易应用"], fallback_index=0)
    result["app_debug"] = app_debug
    if not app_debug.get("options"):
        result["missing_trade_app_options"] = True
        result["final_text"] = frame.locator("body").inner_text(timeout=5000)[:8000]
        return result
    result["missing_trade_app_options"] = False
    app = select_first_open_dropdown_option(frame)
    result["selected_app"] = app
    if not result["selected_app"]:
        result["selected_app"] = click_first_dropdown_option_by_mouse(frame)
    frame.page.wait_for_timeout(1200)
    mid_text = frame.locator("body").inner_text(timeout=5000)
    result["mid_text_after_app"] = mid_text[:4000]
    if "交易通道" in mid_text and "去绑定" in mid_text:
        channel_probe = open_modal_select_dropdown(frame, ["交易通道"], fallback_index=1)
        result["channel_probe_before_bind"] = channel_probe
        if not channel_probe.get("options"):
            result["missing_trade_channel_options"] = True
            result["clicked_channel_go_bind"] = click_text(frame, "去绑定")
            frame.page.wait_for_timeout(2500)
            channel_probe_after_bind = open_modal_select_dropdown(frame, ["交易通道"], fallback_index=1)
            result["channel_probe_after_bind"] = channel_probe_after_bind
            if not channel_probe_after_bind.get("options"):
                result["final_text"] = frame.locator("body").inner_text(timeout=5000)[:8000]
                return result
            channel = select_first_open_dropdown_option(frame)
            result["selected_channel"] = channel
            result["channel_debug"] = channel_probe_after_bind
            if not result["selected_channel"]:
                result["selected_channel"] = click_first_dropdown_option_by_mouse(frame)
            clicked = click_text(frame, "确 认") or click_text(frame, "确认")
            result["clicked_confirm"] = clicked
            frame.page.wait_for_timeout(3000)
            result["final_text"] = frame.locator("body").inner_text(timeout=5000)[:8000]
            return result
        result["missing_trade_channel_options"] = False
    result["clicked_channel_go_bind"] = False
    channel_debug = open_modal_select_dropdown(frame, ["交易通道"], fallback_index=1)
    if not channel_debug.get("options"):
        result["missing_trade_channel_options"] = True
        result["final_text"] = frame.locator("body").inner_text(timeout=5000)[:8000]
        return result
    result["missing_trade_channel_options"] = False
    channel = select_first_open_dropdown_option(frame)
    result["selected_channel"] = channel
    result["channel_debug"] = channel_debug
    if not result["selected_channel"]:
        result["selected_channel"] = click_first_dropdown_option_by_mouse(frame)
    clicked = click_text(frame, "确 认") or click_text(frame, "确认")
    result["clicked_confirm"] = clicked
    frame.page.wait_for_timeout(3000)
    result["final_text"] = frame.locator("body").inner_text(timeout=5000)[:8000]
    return result


def current_finance_frame(page):
    for frame in page.frames:
        if frame == page.main_frame:
            continue
        if any(key in frame.url for key in ["financialConfig", "billManage", "collectionSettlement"]):
            return frame
    for frame in page.frames:
        if frame == page.main_frame:
            continue
        try:
            text = frame.locator("body").inner_text(timeout=1000)
        except Exception:
            continue
        if any(key in text for key in ["财务结算", "结算账单", "员工结算汇总", "线上打款配置"]):
            return frame
    raise AssertionError(f"未找到财务结算 iframe；frames={[frame.url for frame in page.frames]}")


def main() -> None:
    account, order = resolve_wanyouji_owner()
    out_dir = ROOT / "runtime/probes/wanyouji_online_payment_config" / datetime.now().strftime("%Y%m%d-%H%M%S")
    out_dir.mkdir(parents=True, exist_ok=True)

    settings = settings_from_env()
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False, slow_mo=100)
        context = browser.new_context(viewport={"width": settings.viewport_width, "height": settings.viewport_height})
        page = context.new_page()
        owner_page = WanyoujiFulfillmentPage(page, settings.tenant_home_url)
        try:
            owner_page.login_main_account(account.phone, account.password)
            try:
                frame = owner_page.open_workshop_finance_settlement_list()
            except AssertionError:
                page.evaluate(
                    """url => {
                        const frame = Array.from(document.querySelectorAll('iframe'))
                          .find(item => (item.src || '').includes('test-wshop'));
                        if (frame) {
                          frame.src = url;
                          return;
                        }
                        window.location.href = url;
                    }""",
                    "https://test-wshop.lianlianwz.com/financialConfig/billManage",
                )
                page.wait_for_timeout(5000)
                frame = current_finance_frame(page)
            page.screenshot(path=str(out_dir / "01_finance_settlement.png"), full_page=True)

            clicked_bill_tab = click_text(frame, "结算账单列表")
            page.wait_for_timeout(2500)
            page.screenshot(path=str(out_dir / "02_bill_list.png"), full_page=True)

            if not clicked_bill_tab:
                owner_page.click_sidebar_leaf_strict("结算账单列表") or owner_page.click_sidebar_text_scrolled("结算账单列表")
                page.wait_for_timeout(2500)
                frame = current_finance_frame(page)
                page.screenshot(path=str(out_dir / "02b_bill_list_sidebar.png"), full_page=True)

            before = frame.locator("body").inner_text(timeout=5000)
            clicked_config = click_text(frame, "线上打款配置")
            page.wait_for_timeout(2500)
            page.screenshot(path=str(out_dir / "03_online_payment_config.png"), full_page=True)
            after = frame.locator("body").inner_text(timeout=5000)
            details = visible_buttons_and_fields(frame)
            clicked_bind = False
            bind_text = ""
            bind_details = {}
            if "去绑定" in after:
                clicked_bind = click_text(frame, "去绑定")
                page.wait_for_timeout(3500)
                page.screenshot(path=str(out_dir / "04_after_go_bind.png"), full_page=True)
                bind_frame = current_finance_frame(page)
                bind_text = bind_frame.locator("body").inner_text(timeout=5000)
                bind_details = visible_buttons_and_fields(bind_frame)
            save_result = {}
            if os.getenv("E2E_PROBE_SAVE_ONLINE_PAYMENT_CONFIG", "1").strip() not in {"0", "false", "False"}:
                save_frame = current_finance_frame(page)
                save_result = save_online_payment_config_if_possible(save_frame)
                page.screenshot(path=str(out_dir / "05_after_save_config.png"), full_page=True)

            result = {
                "account": {
                    "phone": account.phone,
                    "enterprise_name": account.enterprise_name,
                    "merchant_id": account.merchant_id,
                },
                "order": order,
                "clicked_bill_tab": clicked_bill_tab,
                "clicked_online_payment_config": clicked_config,
                "clicked_go_bind": clicked_bind,
                "frame_url": frame.url,
                "before_text": before[:8000],
                "after_text": after[:8000],
                "details": details,
                "bind_page_url": page.url,
                "bind_text": bind_text[:8000],
                "bind_details": bind_details,
                "save_result": save_result,
            }
            (out_dir / "result.json").write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
            print(out_dir)
            print(json.dumps(result, ensure_ascii=True, indent=2)[:3000])
        finally:
            context.close()
            browser.close()


if __name__ == "__main__":
    main()
