#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：debug.py
模块描述：调试只读 API 路由（语义解析器验证用，无数据库写入）

提供 `/api/debug/resolve`：在真实被测系统页面（TARGET_URL）上对单个步骤
做语义解析，返回选择器链 + 页面识别结果 + save_as 提取值，用于验证
"语义化用例执行"各阶段规则是否按预期命中。

作者：czj
创建日期：2026-08-18
"""

import asyncio
import logging
from typing import Any, Dict, List, Optional

from fastapi import APIRouter
from pydantic import BaseModel, Field

from app.config import settings

logger = logging.getLogger(__name__)

router = APIRouter(tags=["调试"])


# ==================== Schema 定义 ====================

class DebugResolveRequest(BaseModel):
    """语义解析调试请求"""
    url: str = Field(default_factory=lambda: settings.TARGET_URL, description="要打开的页面 URL")
    step: Dict[str, Any] = Field(..., description="步骤定义（含可选 semantic 字段）")
    scope: Optional[str] = Field(None, description="期望页面 scope id（页面注册表，可选）")
    parameters: Dict[str, Any] = Field(default_factory=dict, description="用例参数（渲染语义文本）")


class DebugResolveResponse(BaseModel):
    """语义解析调试响应"""
    ok: bool = Field(..., description="是否解析到选择器链")
    chain: List[str] = Field(default_factory=list, description="解析出的选择器链")
    path: str = Field(default="", description="命中路径: semantic/pattern/keyword/mapping/none")
    page_id: Optional[str] = Field(None, description="运行时识别的页面 scope id")
    page_name: Optional[str] = Field(None, description="运行时识别的页面名称")
    expected_scope: Optional[str] = Field(None, description="步骤声明的期望 scope")
    scope_matched: Optional[bool] = Field(None, description="scope 是否匹配")
    save_as_values: Dict[str, Any] = Field(default_factory=dict, description="save_as 提取的上下文变量")
    message: str = Field(default="", description="说明信息")


# ==================== 同步解析（Playwright 须在线程中执行） ====================

def _resolve_on_live_page(
    url: str,
    step: Dict[str, Any],
    scope: Optional[str],
    parameters: Dict[str, Any],
) -> DebugResolveResponse:
    """
    打开真实页面并做语义解析（同步 API，须在工作线程调用）

    返回值：
        DebugResolveResponse —— 解析结果
    """
    from playwright.sync_api import sync_playwright

    from app.executors.ui_context import ExecutionContext
    from app.services.semantic_resolver import get_semantic_resolver
    from app.services.page_url_service import get_page_url_service

    resp = DebugResolveResponse(ok=False)
    semantic = step.get("semantic") if isinstance(step, dict) else None
    resp.expected_scope = semantic.get("scope") if isinstance(semantic, dict) else None

    try:
        with sync_playwright() as p:
            browser = p.chromium.launch(
                headless=True,
                args=["--ignore-certificate-errors"],
            )
            try:
                page = browser.new_page()
                page.goto(url, wait_until="domcontentloaded", timeout=15000)
                page.wait_for_timeout(1200)

                # 运行时页面身份识别
                rec = get_page_url_service().recognize_current_page(page)
                scope_id = rec["id"] if rec else scope
                resp.page_id = rec["id"] if rec else None
                resp.page_name = rec["name"] if rec else None
                if isinstance(semantic, dict):
                    resp.scope_matched = bool(
                        scope_id and semantic.get("scope") and scope_id == semantic.get("scope")
                    )

                # 语义解析
                ctx = ExecutionContext(parameters or {})
                resolver = get_semantic_resolver()
                chain = resolver.resolve_step(page, step, scope_id, ctx) or []

                # save_as 提取验证
                if chain and isinstance(semantic, dict):
                    resolver.extract_save_as(page, semantic, chain, ctx)

                resp.ok = bool(chain)
                resp.chain = chain
                resp.save_as_values = ctx.all()
                if chain and isinstance(semantic, dict) and semantic.get("type") in (
                    "tab", "row_checkbox", "checkbox", "row",
                ):
                    resp.path = "pattern"
                elif chain and isinstance(semantic, dict):
                    resp.path = "semantic"
                elif chain:
                    resp.path = "keyword"
                else:
                    resp.path = "none"
                resp.message = (
                    f"页面识别: {resp.page_name or '未识别'}({resp.page_id or '-'})"
                    + ("，scope 匹配" if resp.scope_matched else "")
                    + ("" if chain else "；未解析到选择器链")
                )
            finally:
                browser.close()
    except Exception as e:
        resp.ok = False
        resp.message = f"解析失败: {e}"
        logger.warning(f"[debug/resolve] 解析失败: {e}")

    return resp


# ==================== API ====================

@router.post("/resolve", response_model=DebugResolveResponse)
async def debug_resolve(req: DebugResolveRequest) -> DebugResolveResponse:
    """
    语义解析调试（只读，启动一次无头浏览器）

    在真实被测系统页面上运行语义解析器，返回选择器链 + 页面身份 + save_as 值。
    用于阶段验证，不写入任何数据。
    """
    try:
        loop = asyncio.get_event_loop()
        resp = await loop.run_in_executor(
            None,
            _resolve_on_live_page,
            req.url,
            req.step,
            req.scope,
            req.parameters,
        )
        return resp
    except Exception as e:
        logger.error(f"[debug/resolve] 接口异常: {e}")
        return DebugResolveResponse(ok=False, message=f"接口异常: {e}")
