#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称：test_page_url_service.py
模块描述：PageUrlService 单元测试（页面注册表 + 运行时页面身份识别）

覆盖：
- 阶段 2 新功能 recognize_current_page：URL patterns 唯一命中 / fingerprint 兜底 / 未识别 None
- get_scope_page 按 scope id 返回页面配置
- 旧功能 recognize_page（步骤识别直达 URL）不回归

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

import json
import sys
import tempfile
from pathlib import Path

# 加入项目路径，确保可导入
sys.path.insert(0, str(Path(__file__).parent.parent))

from app.services.page_url_service import PageUrlService


# ==================== 测试夹具 ====================

REGISTRY = {
    "version": "2.0",
    "pages": [
        {
            "id": "create_meeting",
            "name": "新建会议",
            "url": "https://192.168.5.44/#/meetingV3?meetingV3=%2FmeetingV3%2F%23%2FCreateMeeting",
            # 收紧：不再用万能 meetingV3，避免把会议详情/列表页误识别为新建页
            "url_patterns": ["meetingV3.*CreateMeeting", "meetingV3.*createMeeting"],
            "fingerprint": [{"selector": ".topic_item input", "min_count": 1}],
            "match_rules": [{"type": "step_name_contains", "value": "新建会议"}],
        },
        {
            "id": "login",
            "name": "登录页",
            "url": "",
            "url_patterns": ["login"],
            "fingerprint": [{"selector": "input[placeholder*='手机号']", "min_count": 1}],
        },
        {
            "id": "home",
            "name": "主页",
            "url": "",
            "url_patterns": ["#/home"],
            "fingerprint": [{"selector": ".home_nav_left", "min_count": 1}],
        },
        {
            "id": "meeting_detail",
            "name": "会议详情",
            "url": "",
            "url_patterns": ["meetingV3.*Detail", "MeetingDetail"],
            # 详情页独有标题元素（.room:has-text 在详情页不存在，实测 0 命中）
            "fingerprint": [{"selector": ".detail_top", "min_count": 1}],
        },
    ],
}


class FakePage:
    """鸭子类型 Playwright Page（只实现 .url / .query_selector_all）"""

    def __init__(self, url: str = "", matches=None):
        self._url = url
        # selector -> 匹配元素数（query_selector_all 返回 [1]*n）
        self._matches = matches or {}

    @property
    def url(self):
        return self._url

    def query_selector_all(self, selector: str):
        count = self._matches.get(selector, 0)
        return [object() for _ in range(count)]


def make_service():
    """创建指向临时注册表文件的 PageUrlService"""
    with tempfile.NamedTemporaryFile(
        "w", suffix=".json", delete=False, encoding="utf-8"
    ) as f:
        json.dump(REGISTRY, f, ensure_ascii=False)
        path = f.name
    svc = PageUrlService(path)
    # 清理临时文件（PageUrlService 已加载进内存）
    import os
    try:
        os.unlink(path)
    except OSError:
        pass
    return svc


# ==================== recognize_current_page：URL patterns ====================


def test_recognize_url_pattern_unique_hit():
    """URL patterns 唯一命中 → 直接返回该页面"""
    svc = make_service()
    page = FakePage(url="https://192.168.5.44/#/meetingV3?x=%2FCreateMeeting")
    rec = svc.recognize_current_page(page)
    assert rec is not None
    assert rec["id"] == "create_meeting"
    assert rec["confidence"] == 1.0


def test_recognize_url_pattern_login():
    """登录页 URL 命中 → login"""
    svc = make_service()
    page = FakePage(url="https://192.168.5.44/platform/2.0.2/#/login")
    rec = svc.recognize_current_page(page)
    assert rec is not None
    assert rec["id"] == "login"


# ==================== 回归：详情页 vs 新建页识别（2026-08-18 修复） ====================


def test_recognize_meeting_detail_page():
    """会议详情页（meetingDetail 小写 + .detail_top 指纹）→ meeting_detail"""
    svc = make_service()
    page = FakePage(
        url="https://192.168.5.44/#/meetingV3?meetingV3=%2FmeetingV3%2F%23%2FmeetingDetail%3FmId%25M2x",
        matches={".detail_top": 1, ".topic_item input": 0},
    )
    rec = svc.recognize_current_page(page)
    assert rec is not None
    assert rec["id"] == "meeting_detail"


def test_recognize_create_page_not_misjudged_as_detail():
    """新建会议页（CreateMeeting 大写）→ create_meeting，绝不误判为 meeting_detail"""
    svc = make_service()
    page = FakePage(
        url="https://192.168.5.44/#/meetingV3?meetingV3=%2FmeetingV3%2F%23%2FCreateMeeting",
        matches={".detail_top": 0, ".topic_item input": 2},
    )
    rec = svc.recognize_current_page(page)
    assert rec is not None
    assert rec["id"] == "create_meeting"


def test_recognize_meeting_list_page_returns_none():
    """会议列表页（meetingAll）不属于任何 scope → None（修复前误判 create_meeting）"""
    svc = make_service()
    page = FakePage(
        url="https://192.168.5.44/#/meetingV3?meetingV3=%2FmeetingV3%2F%23%2FmeetingAll",
        matches={".detail_top": 0, ".topic_item input": 0},
    )
    assert svc.recognize_current_page(page) is None


# ==================== recognize_current_page：fingerprint 兜底 ====================


def test_recognize_fingerprint_fallback():
    """URL 未命中任何 url_patterns，但 DOM 指纹命中 → fingerprint 判存"""
    svc = make_service()
    # create_meeting URL 与 login/home 都含 login（歧义），改用指纹区分
    page = FakePage(
        url="https://example.com/somewhere",
        matches={".topic_item input": 1},
    )
    rec = svc.recognize_current_page(page)
    assert rec is not None
    assert rec["id"] == "create_meeting"


def test_recognize_fingerprint_min_count():
    """fingerprint min_count 未满足 → 不判存"""
    svc = make_service()
    page = FakePage(
        url="https://example.com/unknown",
        matches={".topic_item input": 0, ".home_nav_left": 1},
    )
    # home 指纹命中，create_meeting 的 min_count 未满足
    rec = svc.recognize_current_page(page)
    assert rec is not None
    assert rec["id"] == "home"


def test_recognize_no_match_returns_none():
    """URL 与指纹都未命中 → None（零行为变化）"""
    svc = make_service()
    page = FakePage(url="https://example.com/random", matches={})
    assert svc.recognize_current_page(page) is None


def test_recognize_empty_url():
    """URL 为空 → 仅靠指纹识别"""
    svc = make_service()
    page = FakePage(url="", matches={".home_nav_left": 2})
    rec = svc.recognize_current_page(page)
    assert rec is not None
    assert rec["id"] == "home"


# ==================== get_scope_page ====================


def test_get_scope_page():
    """按 scope id 返回页面配置；不存在返回 None"""
    svc = make_service()
    cfg = svc.get_scope_page("create_meeting")
    assert cfg is not None
    assert cfg["id"] == "create_meeting"
    assert svc.get_scope_page("not_exists") is None


# ==================== 旧功能不回归 ====================


def test_recognize_page_legacy_still_works():
    """步骤识别（直达 URL）老功能不回归；无直达 URL 的页面不参与"""
    svc = make_service()
    steps = [{"order": 1, "name": "进入新建会议", "action": "click", "params": {"selector": "新建会议"}}]
    rec = svc.recognize_page(steps)
    assert rec is not None
    assert rec["page_id"] == "create_meeting"
    assert rec["url"].startswith("https")


def test_recognize_page_ignores_no_url_pages():
    """login/home 无直达 URL，不应被步骤识别选中"""
    svc = make_service()
    steps = [{"order": 1, "name": "登录页", "action": "click", "params": {}}]
    rec = svc.recognize_page(steps)
    # 无 url 的页面被排除；无 match_rules 匹配时返回 None
    assert rec is None or rec["page_id"] == "create_meeting"
