from __future__ import annotations

import json
import os
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, WanyoujiMemberData, WanyoujiMemberPage
from e2e.utils.account_store import account_store
from e2e.utils.verification_code import FeishuConfig, VerificationCodeProvider


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


def main() -> None:
    load_dotenv()
    store = account_store()
    customer = latest_customer_service_account(store)
    parent = store.find(phone=customer.parent_phone)
    if parent is None:
        raise SystemExit(f"未找到客服子账号 {customer.phone} 的主账号：{customer.parent_phone}")

    provider = VerificationCodeProvider(
        mode=os.getenv("E2E_VERIFICATION_CODE_MODE", "feishu"),
        static_code=os.getenv("E2E_VERIFICATION_CODE_STATIC", ""),
        manual_code_file=os.getenv("E2E_MANUAL_CODE_FILE", "runtime/sms_code.txt"),
        manual_timeout_seconds=int(os.getenv("E2E_MANUAL_CODE_TIMEOUT_SECONDS", "180")),
        manual_poll_interval_seconds=int(os.getenv("E2E_MANUAL_CODE_POLL_INTERVAL_SECONDS", "2")),
        feishu_config=FeishuConfig(
            app_id=os.getenv("FEISHU_APP_ID", ""),
            app_secret=os.getenv("FEISHU_APP_SECRET", ""),
            chat_id=os.getenv("FEISHU_CHAT_ID", ""),
            timeout_seconds=int(os.getenv("FEISHU_TIMEOUT_SECONDS", "60")),
            poll_interval_seconds=int(os.getenv("FEISHU_POLL_INTERVAL_SECONDS", "3")),
        ),
    )

    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()
        member_page = WanyoujiMemberPage(page, TENANT_HOME_URL, code_provider=provider)

        member_page.login_main_account(parent.phone, parent.password)
        frame = member_page.open_member_management()
        member = WanyoujiMemberData(
            phone=customer.phone,
            password=customer.password,
            role=customer.member_role,
            name=customer.contact_name,
            product="丸友集",
        )
        member_page.confirm_member_invitation_if_needed(frame, member)
        dump_page(page, "01_主账号成员管理确认后")

        member_page.verify_member_can_login_by_sms(member)
        dump_page(page, "02_客服验证码登录后")

        select_sub_account_if_present(page, customer.phone, customer.contact_name)
        dump_page(page, "03_客服选择子账号后")

        page.goto(WANYOUJI_CONSOLE_URL, wait_until="domcontentloaded")
        page.wait_for_timeout(5000)
        dump_page(page, "04_客服丸友集控制台菜单")

        context.close()
        browser.close()

    store.mark_wanyouji_member_created(
        parent=parent,
        phone=customer.phone,
        password=customer.password,
        role=customer.member_role,
        contact_name=customer.contact_name,
        status="registered",
        invite_url=customer.member_invite_url,
    )
    print(f"客服注册确认与验证码登录验证完成：{customer.phone} / {customer.contact_name}")
    print(f"探测结果目录：{OUT_DIR.resolve()}")


def latest_customer_service_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("台账中没有可验证的客服子账号")
    return account


def select_sub_account_if_present(page: Page, phone: str, name: str) -> None:
    body_text = page.locator("body").inner_text(timeout=5000)
    if "/account/select" not in page.url and "选择账号" not in body_text:
        return
    box = page.evaluate(
        """({ phone, name }) => {
            const cards = Array.from(document.querySelectorAll('taro-view-core, div'))
              .map(el => ({ text: el.innerText || el.textContent || '', r: el.getBoundingClientRect() }))
              .filter(item => item.r.width > 260 && item.r.width < 760 && item.r.height > 45 && item.r.height < 180)
              .filter(item => item.text.includes('子账号') || item.text.includes('客服') || item.text.includes(phone) || item.text.includes(name))
              .sort((a, b) => (a.r.width * a.r.height) - (b.r.width * b.r.height));
            const target = cards[0];
            if (!target) return null;
            return { x: target.r.x, y: target.r.y, width: target.r.width, height: target.r.height, text: target.text };
        }""",
        {"phone": phone, "name": name},
    )
    if not box:
        raise AssertionError(f"验证码登录后未找到客服子账号卡片；text={body_text[:1200]}")
    page.mouse.click(box["x"] + box["width"] / 2, box["y"] + box["height"] / 2)
    page.wait_for_load_state("domcontentloaded")
    page.wait_for_timeout(3000)


def dump_page(page: Page, name: str) -> None:
    payload = {
        "url": page.url,
        "text": safe_page_text(page)[:4000],
        "nodes": safe_nodes(page),
        "frames": [
            {
                "url": frame.url,
                "text": safe_frame_text(frame)[:3000],
                "nodes": safe_frame_nodes(frame),
            }
            for frame in page.frames
            if frame.url != "about:blank"
        ],
    }
    (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_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, 600)"""


if __name__ == "__main__":
    main()
