"""HEL-174: 公司端往来确认完成态用 success 绿，待确认保留 warn 黄。"""

from __future__ import annotations

import re
import threading
import unittest
from functools import partial
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
WEB = ROOT / "web"

try:
    from playwright.sync_api import sync_playwright
except ImportError:  # pragma: no cover - 默认测试环境无浏览器依赖
    sync_playwright = None


class ConfirmStatusSourceContractTests(unittest.TestCase):
    """不依赖浏览器：锁住完成态切绿 / 待确认仍黄的实现契约。"""

    def test_app_js_toggles_success_green_when_pending_zero(self) -> None:
        js = (WEB / "app.js").read_text(encoding="utf-8")
        self.assertIn('pill ${total ? "pill-warn" : "pill-success"}', js)
        self.assertIn('flowStep.classList.toggle("doing", pending > 0)', js)
        self.assertIn('flowStep.classList.toggle("done", pending === 0)', js)
        self.assertIn('notice.classList.toggle("warn", pending > 0)', js)
        self.assertIn('notice.classList.toggle("success", pending === 0)', js)
        # 文案：完成=已完成，不把待确认一并改绿
        self.assertRegex(js, r'status\.textContent = total \? `\$\{total\} 项待处理` : "已完成"')
        self.assertRegex(js, r'flowState\.textContent = pending \? `待处理 \$\{pending\} 笔` : "已完成"')

    def test_design_tokens_map_done_to_success_doing_to_warn(self) -> None:
        css = (WEB / "design-system.css").read_text(encoding="utf-8")
        self.assertIn("--success:", css)
        self.assertIn("--warn:", css)
        self.assertIn(".flow-step.done .fs-dot { background: var(--success); }", css)
        self.assertIn(".flow-step.done .fs-state { color: var(--success); }", css)
        self.assertIn(".flow-step.doing .fs-dot { background: var(--warn);", css)
        self.assertIn(".pill-success { background: var(--success-soft); color: var(--success); }", css)
        self.assertIn(".pill-warn { background: var(--warn-soft);", css)
        self.assertIn(".notice.success { background: var(--success-soft);", css)
        self.assertIn(".notice.warn { background: var(--warn-soft);", css)

    def test_company_html_exposes_flow_step_and_cache_bust(self) -> None:
        html = (WEB / "company.html").read_text(encoding="utf-8")
        self.assertIn('id="workspaceConfirmState"', html)
        self.assertIn('id="workspacePendingStatus"', html)
        self.assertIn('id="workspaceFlowSub"', html)
        self.assertIn('data-view-link="reconcile"', html)
        self.assertIn("app.js?v=13", html)
        # 静态初值仍为进行中（黄），由 JS 在 pending=0 时切 done
        self.assertRegex(html, r'class="flow-step doing"[^>]*data-view-link="reconcile"')


def _extract_fn(source: str, name: str) -> str:
    marker = f"function {name}("
    start = source.index(marker)
    depth = 0
    for i, ch in enumerate(source[start:], start):
        if ch == "{":
            depth += 1
        elif ch == "}":
            depth -= 1
            if depth == 0:
                return source[start : i + 1]
    raise AssertionError(f"未能截取 function {name}")


def _chromium_available() -> bool:
    """本机缺 libatk 等系统库时 chromium 无法启动。"""
    try:
        import ctypes.util

        return bool(ctypes.util.find_library("atk-1.0"))
    except Exception:
        return False


@unittest.skipUnless(sync_playwright, "playwright 未安装，跳过真实 DOM 色值校验")
@unittest.skipUnless(_chromium_available(), "系统缺少 chromium 依赖库（如 libatk），跳过浏览器色值校验")
class ConfirmStatusDomTests(unittest.TestCase):
    """真实浏览器：pending>0 为黄，pending=0 为绿。"""

    @classmethod
    def setUpClass(cls) -> None:
        handler = partial(SimpleHTTPRequestHandler, directory=str(WEB))
        cls.httpd = ThreadingHTTPServer(("127.0.0.1", 0), handler)
        cls.port = cls.httpd.server_address[1]
        cls.thread = threading.Thread(target=cls.httpd.serve_forever, daemon=True)
        cls.thread.start()
        cls.base = f"http://127.0.0.1:{cls.port}"
        app_js = (WEB / "app.js").read_text(encoding="utf-8")
        cls.inject_js = "\n".join(
            [
                "const state = {};",
                "function $(sel, root) { return (root || document).querySelector(sel); }",
                "function $$(sel, root) { return Array.from((root || document).querySelectorAll(sel)); }",
                _extract_fn(app_js, "formatWorkspaceAmount"),
                _extract_fn(app_js, "applyCompanyWorkspace"),
            ]
        )

    @classmethod
    def tearDownClass(cls) -> None:
        cls.httpd.shutdown()
        cls.httpd.server_close()

    def _open_fixture(self, page):
        page.goto(f"{self.base}/design-system.css", wait_until="domcontentloaded")
        page.set_content(
            f"""<!doctype html>
<html lang="zh-CN"><head>
<meta charset="UTF-8"/>
<link rel="stylesheet" href="{self.base}/design-system.css"/>
</head>
<body data-portal="company">
  <button id="workspaceUnilateralCta" class="btn btn-primary">去确认单边流水 (0)</button>
  <div class="card" id="workspaceTodos">
    <div class="card-head">
      <span class="card-title">本月待办<span class="sub" id="workspaceTodoSub">…</span></span>
      <span class="pill pill-warn" id="workspacePendingStatus">加载中</span>
    </div>
    <div id="workspaceTodoList"></div>
    <div class="table-foot" id="workspaceTodoFoot"><span>…</span></div>
  </div>
  <div class="card">
    <div class="card-head">
      <span class="card-title">账期流程<span class="sub" id="workspaceFlowSub">…</span></span>
    </div>
    <div class="flow">
      <a class="flow-step doing" data-view-link="reconcile" href="#reconcile">
        <div class="fs-top"><span class="fs-idx">03</span><span class="fs-dot"></span><span class="fs-name">往来确认</span></div>
        <div class="fs-state" id="workspaceConfirmState">加载中…</div>
        <div class="fs-meta" id="workspaceConfirmMeta">…</div>
      </a>
    </div>
  </div>
  <div class="notice warn" id="blocking-notice">
    <div class="n-title" id="notice-title">…</div>
    <div class="n-body" id="notice-body">…</div>
  </div>
  <span class="tab-count" id="count-match">0</span>
  <nav class="side-nav"><a data-view="reconcile"><span class="nav-badge">0</span></a></nav>
</body></html>""",
            wait_until="domcontentloaded",
        )
        page.add_script_tag(content=self.inject_js)
        page.wait_for_function("() => typeof applyCompanyWorkspace === 'function'")

    @staticmethod
    def _colors(page) -> dict:
        return page.evaluate(
            """() => {
              const step = document.querySelector('.flow-step[data-view-link="reconcile"]');
              const state = document.getElementById('workspaceConfirmState');
              const pill = document.getElementById('workspacePendingStatus');
              const notice = document.getElementById('blocking-notice');
              const cs = (el) => getComputedStyle(el);
              return {
                stepClass: step.className,
                stateText: state.textContent,
                stateColor: cs(state).color,
                pillClass: pill.className,
                pillColor: cs(pill).color,
                pillText: pill.textContent,
                noticeClass: notice.className,
              };
            }"""
        )

    def test_pending_stays_warn_completed_turns_success(self) -> None:
        with sync_playwright() as p:
            browser = p.chromium.launch(headless=True, args=["--no-sandbox"])
            page = browser.new_page(viewport={"width": 1440, "height": 900})
            self._open_fixture(page)

            page.evaluate(
                """() => applyCompanyWorkspace({
                  pending_unilateral: 2,
                  pending_total: 2,
                  unilateral_events: [
                    {event_id: 1, amount: '100.00', currency: 'CNY',
                     counterparty_company_name: '乙', effective_at: '2026-07-01'}
                  ]
                })"""
            )
            pending = self._colors(page)
            self.assertIn("doing", pending["stepClass"])
            self.assertNotIn("done", pending["stepClass"].split())
            self.assertIn("pill-warn", pending["pillClass"])
            self.assertNotIn("pill-success", pending["pillClass"])
            self.assertIn("warn", pending["noticeClass"].split())
            self.assertIn("待处理", pending["stateText"])
            self.assertIn("待处理", pending["pillText"])

            page.evaluate(
                """() => applyCompanyWorkspace({
                  pending_unilateral: 0,
                  pending_total: 0,
                  unilateral_events: []
                })"""
            )
            done = self._colors(page)
            self.assertIn("done", done["stepClass"])
            self.assertNotIn("doing", done["stepClass"].split())
            self.assertIn("pill-success", done["pillClass"])
            self.assertNotIn("pill-warn", done["pillClass"])
            self.assertIn("success", done["noticeClass"].split())
            self.assertEqual("已完成", done["stateText"])
            self.assertEqual("已完成", done["pillText"])

            self.assertNotEqual(pending["stateColor"], done["stateColor"])
            self.assertNotEqual(pending["pillColor"], done["pillColor"])
            browser.close()


if __name__ == "__main__":
    unittest.main()
