#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
元素映射表服务单元测试

验证 ElementMappingService 的三种匹配策略：
- 精确匹配评分：解决"会议预约"歧义（应匹配筛选器-会议预约 而非 会议服务统计）
- 模糊匹配（jieba + SequenceMatcher ≥0.7）
- 上下文辅助匹配
- 容错：JSON 缺失时自动禁用
"""

import json
import os
import sys
import tempfile

import pytest

sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "app"))

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


@pytest.fixture(autouse=True)
def _legacy_fuzzy_enabled():
    """阶段 5 起 USE_FUZZY_MAPPING 默认 false（模糊评分关停，改用 v2 显式字典）。

    本模块测试的是 legacy 模糊评分路径——显式开启该 flag，既保留对旧算法
    的回归覆盖，也验证"部署可一键回切"的逃生舱仍然有效。
    """
    old = settings.USE_FUZZY_MAPPING
    settings.USE_FUZZY_MAPPING = True
    yield
    settings.USE_FUZZY_MAPPING = old


@pytest.fixture
def meeting_mapping(tmp_path):
    """创建会议流程 P1 步骤映射（含干扰项，验证评分能正确区分）"""
    data = {
        "version": "1.0",
        "updated_at": "2026-08-18",
        "source": "test",
        "elements": {
            # 3 个预存问题的目标条目
            "创建会议-会议室列表": {"selector": ".el-table", "type": "css", "tier": "创建会议", "description": "创建会议 - 右侧会议室表格"},
            "会议室-房间勾选": {"selector": ".room_item .el-checkbox", "type": "css", "tier": "会议室", "description": "会议室 - 房间卡片勾选复选框"},
            "创建会议-选择时间": {"selector": ".time_btn .btns", "type": "css", "tier": "创建会议", "description": "创建会议 - 时间选择按钮组"},
            # P1 表步骤 13-21 目标条目
            "创建会议-议题输入框": {"selector": ".topic_item input", "type": "css", "tier": "创建会议", "description": "创建会议 - 议题输入框"},
            "创建会议-编辑会议室": {"selector": ".room .delimg", "type": "css", "tier": "创建会议", "description": "创建会议 - 参会人配置模式开关（编辑/完成共用）"},
            "创建会议-完成按钮": {"selector": ".room .delimg", "type": "css", "tier": "创建会议", "description": "创建会议 - 参会人配置完成按钮（v2 显式条目，替代模糊评分特例）"},
            "创建会议-参会人勾选": {"selector": ".el-table:has-text('姓名') .el-checkbox", "type": "css", "tier": "创建会议", "description": "创建会议 - 参会人表格复选框"},
            "创建会议-确定创建": {"selector": ".btn button.el-button", "type": "css", "tier": "创建会议", "description": "创建会议 - 底部确定创建按钮"},
            "会议创建成功-查看详情": {"selector": ".el-message-box button:has-text('查看详情')", "type": "css", "tier": "会议创建成功", "description": "创建成功弹窗查看详情按钮"},
            "会议详情-取消会议": {"selector": ".buttom_btn .el-button--info.is-plain", "type": "css", "tier": "会议详情", "description": "会议详情 - 底部取消会议按钮"},
            "会议详情-取消确认确定": {"selector": ".el-dialog:visible .el-dialog__footer .el-button--primary", "type": "css", "tier": "会议详情", "description": "取消会议二次确认弹窗确定按钮"},
            # 干扰项（验证评分能区分）
            "首页-置顶功能-会议室列表": {"selector": '[data-key="meetingRoomList"]', "type": "css", "tier": "首页", "description": "会议室列表导航入口"},
            "全部功能-预定2.0-会议室列表": {"selector": '[data-id="meetingRoomList"]', "type": "css", "tier": "全部功能", "description": "预定2.0会议室列表"},
            "会议室-区域树": {"selector": ".area_block", "type": "css", "tier": "会议室", "description": "会议室 - 左侧区域树"},
            "创建会议-参会人表格": {"selector": ".el-table .el-checkbox", "type": "css", "tier": "创建会议", "description": "创建会议 - 参会人表格"},
            "会议详情-操作按钮": {"selector": ".buttom_btn", "type": "css", "tier": "会议详情", "description": "会议详情 - 底部操作按钮"},
            "筛选-确定": {"selector": ".RoomFilter .filter_footer .el-button--primary", "type": "css", "tier": "会议室筛选", "description": "会议室筛选弹窗确定按钮"},
            "创建会议-新建会议": {"selector": 'div:visible:has-text("新建会议")', "type": "css", "tier": "创建会议", "description": "顶部新建会议按钮"},
            "创建会议-人员配置按钮": {"selector": ".personnelConfigure", "type": "css", "tier": "创建会议", "description": "右侧参会人配置按钮"},
            "会议列表-全部-时间": {"selector": ".select_date .el-date-picker[type=datetimerange]", "type": "css", "tier": "会议列表", "description": "会议列表时间范围选择"},
        },
    }
    mapping_file = tmp_path / "meeting_mapping.json"
    mapping_file.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
    return str(mapping_file)


@pytest.fixture
def sample_mapping(tmp_path):
    """创建一份最小映射表用于测试（避免依赖完整 JSON）"""
    data = {
        "version": "1.0",
        "updated_at": "2026-08-11",
        "source": "test",
        "elements": {
            "导航栏-功能中心抽屉开关": {"selector": ".home_nav_left", "type": "css", "tier": "首页导航栏", "description": "打开功能中心抽屉"},
            "全部功能-筛选器-会议预约": {"selector": "#reserve_enable", "type": "css", "tier": "功能中心-筛选器", "description": "会议预约"},
            "全部功能-筛选器-数据统计": {"selector": "#data_enable", "type": "css", "tier": "功能中心-筛选器", "description": "数据统计"},
            "首页-常用功能-创建会议": {"selector": '[data-key="reserve_list.create"]', "type": "css", "tier": "首页功能块", "description": "创建会议"},
            "首页-常用功能-会议室": {"selector": '[data-key="reserve_list.room"]', "type": "css", "tier": "首页功能块", "description": "会议室"},
            "首页-常用功能-会议列表": {"selector": '[data-key="reserve_list.meeting"]', "type": "css", "tier": "首页功能块", "description": "会议列表"},
            "全部功能-数据统计-会议服务统计": {"selector": '[data-id="data_list.meeting_service"]', "type": "css", "tier": "功能中心-全部功能", "description": "会议服务统计"},
            "全部功能-数据统计-会议统计": {"selector": '[data-id="data_list.meeting_total"]', "type": "css", "tier": "功能中心-全部功能", "description": "会议统计"},
            "功能中心-搜索输入框": {"selector": ".menu_header .search input", "type": "css", "tier": "功能中心-搜索框", "description": "功能中心搜索框"},
        },
    }
    mapping_file = tmp_path / "elements_mapping.json"
    mapping_file.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
    return str(mapping_file)


def _make_service(mapping_file):
    return ElementMappingService(mapping_file)


class TestExactMatch:
    """精确匹配测试 - 评分公式"""

    def test_classification_meeting_preference(self, sample_mapping):
        """'会议预约'分类应匹配筛选器-会议预约，而非会议服务统计"""
        svc = _make_service(sample_mapping)
        result = svc.get_selector("点击【会议预约】分类", "click")
        assert result is not None
        assert result["selector"] == "#reserve_enable"

    def test_click_meeting_service_preference(self, sample_mapping):
        """'会议服务统计'应匹配对应统计项"""
        svc = _make_service(sample_mapping)
        result = svc.get_selector("点击【会议服务统计】", "click")
        assert result is not None
        assert result["selector"] == '[data-id="data_list.meeting_service"]'

    def test_create_meeting(self, sample_mapping):
        """'新建会议'应通过同义词匹配到创建会议"""
        svc = _make_service(sample_mapping)
        result = svc.get_selector("点击【新建会议】按钮", "click")
        assert result is not None
        assert result["selector"] == '[data-key="reserve_list.create"]'

    def test_drawer(self, sample_mapping):
        """'功能中心抽屉'应匹配导航栏开关"""
        svc = _make_service(sample_mapping)
        result = svc.get_selector("点击功能中心抽屉展开", "click")
        assert result is not None
        assert result["selector"] == ".home_nav_left"

    def test_search(self, sample_mapping):
        """搜索输入框匹配"""
        svc = _make_service(sample_mapping)
        result = svc.get_selector("输入搜索关键词", "fill")
        assert result is not None
        assert result["selector"] == ".menu_header .search input"


class TestFaultTolerance:
    """容错测试"""

    def test_missing_file(self, tmp_path):
        """映射表文件不存在时，服务禁用但不抛异常"""
        svc = ElementMappingService(str(tmp_path / "nonexistent.json"))
        assert not svc.is_enabled()
        assert svc.get_selector("点击【会议预约】分类", "click") is None

    def test_invalid_json(self, tmp_path):
        """映射表 JSON 损坏时，服务禁用"""
        bad_file = tmp_path / "bad.json"
        bad_file.write_text("{invalid json", encoding="utf-8")
        svc = ElementMappingService(str(bad_file))
        assert not svc.is_enabled()

    def test_empty_elements(self, tmp_path):
        """elements 为空时禁用"""
        empty_file = tmp_path / "empty.json"
        empty_file.write_text(json.dumps({"elements": {}}, ensure_ascii=False), encoding="utf-8")
        svc = ElementMappingService(str(empty_file))
        assert not svc.is_enabled()
        assert svc.get_selector("点击任意", "click") is None


class TestPresetMapping:
    """3 个预存映射问题修复回归测试"""

    def test_meeting_room_list(self, meeting_mapping):
        """'点击会议室列表'应命中创建会议页会议室表格，而非首页导航入口"""
        svc = _make_service(meeting_mapping)
        result = svc.get_selector("点击会议室列表", "click")
        assert result is not None
        assert result["key"] == "创建会议-会议室列表"
        assert result["selector"] == ".el-table"

    def test_check_room(self, meeting_mapping):
        """'勾选房间'应命中会议室房间卡片复选框"""
        svc = _make_service(meeting_mapping)
        result = svc.get_selector("勾选房间", "check")
        assert result is not None
        assert result["key"] == "会议室-房间勾选"

    def test_select_time(self, meeting_mapping):
        """'点击选择时间'应命中创建会议时间按钮组，而非会议列表时间范围"""
        svc = _make_service(meeting_mapping)
        result = svc.get_selector("点击选择时间", "click")
        assert result is not None
        assert result["key"] == "创建会议-选择时间"


class TestP1MeetingFlow:
    """创建会议流程 P1 表步骤 13-21 映射回归测试"""

    def test_topic_input(self, meeting_mapping):
        """'会议议题输入'应命中议题输入框，而非新建会议按钮"""
        svc = _make_service(meeting_mapping)
        result = svc.get_selector("会议议题输入：自动化新建会议的议题", "fill")
        assert result is not None
        assert result["key"] == "创建会议-议题输入框"

    def test_edit_button(self, meeting_mapping):
        """'编辑按钮'应命中 .room .delimg（参会人配置模式开关）"""
        svc = _make_service(meeting_mapping)
        result = svc.get_selector("点击左侧会议室框的编辑按钮", "click")
        assert result is not None
        assert result["key"] == "创建会议-编辑会议室"
        assert result["selector"] == ".room .delimg"

    def test_complete_button(self, meeting_mapping):
        """'完成按钮'走 v2 显式字典 → .room .delimg（阶段 5 不再靠模糊评分特例）"""
        svc = _make_service(meeting_mapping)
        result = svc.lookup_by_key("创建会议", "完成按钮")
        assert result is not None
        assert result["selectors"] == [".room .delimg"]

    def test_check_user(self, meeting_mapping):
        """'勾选右侧的用户'应命中参会人表格复选框，而非参会人表格或编辑按钮"""
        svc = _make_service(meeting_mapping)
        result = svc.get_selector("勾选右侧的用户：admin@xty12", "check")
        assert result is not None
        assert result["key"] == "创建会议-参会人勾选"

    def test_confirm_create(self, meeting_mapping):
        """'确定创建'应命中底部确定创建按钮"""
        svc = _make_service(meeting_mapping)
        result = svc.get_selector("点击【确定创建】按钮", "click")
        assert result is not None
        assert result["key"] == "创建会议-确定创建"

    def test_view_detail(self, meeting_mapping):
        """'查看详情'应命中创建成功弹窗按钮"""
        svc = _make_service(meeting_mapping)
        result = svc.get_selector("点击弹窗的【查看详情】按钮", "click")
        assert result is not None
        assert result["key"] == "会议创建成功-查看详情"

    def test_cancel_meeting(self, meeting_mapping):
        """'取消会议'应命中详情页底部取消按钮，而非操作按钮区"""
        svc = _make_service(meeting_mapping)
        result = svc.get_selector("点击底部的【取消会议】按钮", "click")
        assert result is not None
        assert result["key"] == "会议详情-取消会议"

    def test_cancel_confirm(self, meeting_mapping):
        """'二次确认确定'应命中取消会议二次确认弹窗确定按钮，而非筛选弹窗"""
        svc = _make_service(meeting_mapping)
        result = svc.get_selector("点击二次确认弹窗的【确定】按钮", "click")
        assert result is not None
        assert result["key"] == "会议详情-取消确认确定"


class TestNoMatch:
    """未命中时返回 None"""

    def test_unrelated_step(self, sample_mapping):
        """映射表未覆盖的步骤返回 None"""
        svc = _make_service(sample_mapping)
        result = svc.get_selector("删除某条记录", "click")
        assert result is None


if __name__ == "__main__":
    sys.exit(pytest.main(["-v", __file__]))