#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：test_element_mapping_v2.py
模块描述：映射表 v2 显式字典单元测试（阶段 5）

覆盖：
- lookup_by_key：v1 flat 自动归组 / scope 别名对齐 / 页前缀键 / 共享元素 / 缺失
- USE_FUZZY_MAPPING 关停：_exact_match/_fuzzy_match 返回 None
- v2 显式 pages 结构加载（前端开发提供格式）
- _build_v2_result 归一

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

import json
import tempfile
import os
from unittest.mock import MagicMock

import pytest

from app.config import settings
from app.services.element_mapping_service import ElementMappingService


@pytest.fixture(scope="module")
def v1_service():
    """加载真实 v1 映射表（231 条），验证自动归组"""
    s = ElementMappingService()
    assert s.is_enabled()
    return s


# ==================== v1 自动归组 → lookup_by_key ====================

class TestLookupByKeyV1:
    def test_create_meeting_exact_semantic_key(self, v1_service):
        """scope 别名 + 语义键 → 确定性选择器链（无评分）"""
        r = v1_service.lookup_by_key("create_meeting", "预定会议")
        assert r is not None
        assert r["selectors"] and isinstance(r["selectors"], list)
        assert r["key"] == "预定会议"

    def test_participant_checkbox_chain(self, v1_service):
        """参会人勾选 → 行级复选框选择器链（此前靠评分规则才能命中）"""
        r = v1_service.lookup_by_key("创建会议", "参会人勾选")
        assert r is not None
        assert any(".el-checkbox" in s for s in r["selectors"])

    def test_login_tier_alias(self, v1_service):
        """page_id 'login' → tier 别名 '登录页' → 命中"""
        r = v1_service.lookup_by_key("login", "密码登录Tab")
        assert r is not None
        assert r["selectors"]

    def test_missing_key_returns_none(self, v1_service):
        assert v1_service.lookup_by_key("create_meeting", "不存在的键XYZ") is None

    def test_missing_page_returns_none(self, v1_service):
        assert v1_service.lookup_by_key("不存在的页", "预定会议") is None

    def test_none_page_id_returns_none(self, v1_service):
        assert v1_service.lookup_by_key(None, "预定会议") is None


# ==================== 评分路径关停 ====================

class TestFuzzyGating:
    def test_exact_match_disabled(self, v1_service):
        old = settings.USE_FUZZY_MAPPING
        settings.USE_FUZZY_MAPPING = False
        try:
            assert v1_service._exact_match("点击预定会议") is None
            assert v1_service._fuzzy_match("点击预定会议") is None
        finally:
            settings.USE_FUZZY_MAPPING = old

    def test_get_selector_gated(self, v1_service):
        old = settings.USE_FUZZY_MAPPING
        settings.USE_FUZZY_MAPPING = False
        try:
            assert v1_service.get_selector("点击预定会议") is None
        finally:
            settings.USE_FUZZY_MAPPING = old

    def test_get_selector_enabled_when_flag_on(self, v1_service):
        old = settings.USE_FUZZY_MAPPING
        settings.USE_FUZZY_MAPPING = True
        try:
            assert v1_service.get_selector("点击预定会议") is not None
        finally:
            settings.USE_FUZZY_MAPPING = old


# ==================== v2 显式结构加载 ====================

class TestV2ExplicitStructure:
    def _write_v2(self):
        data = {
            "version": "2.0",
            "source": "前端开发提供",
            "pages": [
                {
                    "id": "create_meeting",
                    "name": "新建会议",
                    "elements": {
                        "预定会议": {"selectors": ['.time_btn .btns:has-text("预定会议")'], "type": "tab"},
                        "参会人勾选": {
                            "selectors": ['.el-table__row:has-text("admin@xty12") .el-checkbox'],
                            "type": "row_checkbox",
                        },
                    },
                },
                {
                    "id": "meeting_detail",
                    "name": "会议详情",
                    "elements": {
                        "取消会议": {"selectors": ["button:has-text(\"取消会议\")"], "type": "button"},
                    },
                },
            ],
            "shared_elements": {
                "登录按钮": {"selectors": ["button:has-text(\"登录\")"], "type": "button"},
            },
        }
        fd, path = tempfile.mkstemp(suffix=".json")
        with os.fdopen(fd, "w", encoding="utf-8") as f:
            json.dump(data, f, ensure_ascii=False)
        return path

    def test_explicit_pages_lookup(self):
        path = self._write_v2()
        try:
            s = ElementMappingService(path)
            assert s.is_enabled()
            r = s.lookup_by_key("create_meeting", "预定会议")
            assert r["selectors"] == ['.time_btn .btns:has-text("预定会议")']
            assert r["type"] == "tab"
            r2 = s.lookup_by_key("meeting_detail", "取消会议")
            assert r2["selectors"] == ['button:has-text("取消会议")']
        finally:
            os.remove(path)

    def test_shared_elements_fallback(self):
        path = self._write_v2()
        try:
            s = ElementMappingService(path)
            r = s.lookup_by_key("create_meeting", "登录按钮")
            assert r["selectors"] == ['button:has-text("登录")']
        finally:
            os.remove(path)


# ==================== _build_v2_result 归一 ====================

class TestBuildV2Result:
    def _s(self):
        return ElementMappingService.__new__(ElementMappingService)

    def test_string_entry(self):
        r = ElementMappingService._build_v2_result(".btn", "x")
        assert r["selectors"] == [".btn"]
        assert r["type"] == "css"

    def test_dict_with_selectors(self):
        r = ElementMappingService._build_v2_result(
            {"selectors": [".a", ".b"], "type": "tab"}, "x"
        )
        assert r["selectors"] == [".a", ".b"]
        assert r["type"] == "tab"

    def test_dict_with_single_selector(self):
        r = ElementMappingService._build_v2_result({"selector": ".a"}, "x")
        assert r["selectors"] == [".a"]
