#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
TitleMatcher 标题位置计算测试用例集
用于多智能体架构测试验证

执行方式:
    python -m pytest test_title_matcher_fix.py -v
    或
    python test_title_matcher_fix.py
"""

import sys
import os
from pathlib import Path

project_root = Path(__file__).parent.parent.parent
sys.path.insert(0, str(project_root))
os.chdir(project_root)

import re
import fitz
import pytest
from typing import List, Dict, Tuple
from core.construction_review.component.doc_worker.utils.title_matcher import TitleMatcher


class TestTitlePositionFix:
    """标题位置计算修复测试套件"""

    @pytest.fixture(scope="class")
    def matcher(self):
        """提供 TitleMatcher 实例"""
        return TitleMatcher()

    @pytest.fixture(scope="class")
    def test_doc_text(self):
        """加载测试文档全文"""
        test_file = 'D:/wx_work/sichuan_luqiao/lu_sgsc_testfile/测试模版-四川路桥专项施工方案框架以及编制说明（2025修订第三版）- v0.2.pdf'
        if not Path(test_file).exists():
            pytest.skip(f"测试文件不存在: {test_file}")

        doc = fitz.open(test_file)
        full_text = ""
        for page in doc:
            full_text += page.get_text()
        doc.close()
        return full_text

    # ==================== 核心修复测试 ====================

    def test_chapter_10_no_false_positives(self, matcher, test_doc_text):
        """
        测试: 第十章标题位置查找不应包含假阳性

        期望: 只返回目录页和正文页两个位置，不包含"放置于第十章"的引用位置
        """
        title = "第十章 其他资料"
        positions = matcher._find_full_title_positions(title, test_doc_text)

        # 已知的问题位置（引用位置）
        false_positive_positions = [14328, 39690]

        # 验证没有假阳性
        for fp_pos in false_positive_positions:
            assert fp_pos not in positions, \
                f"发现假阳性位置 {fp_pos}，该位置上下文包含引用关键词"

        # 验证找到了正确的位置（应该至少有1-2个）
        assert len(positions) >= 1, "应该至少找到1个标题位置"
        assert len(positions) <= 3, f"标题位置过多，可能包含假阳性: {positions}"

    def test_chapter_10_position_context(self, matcher, test_doc_text):
        """
        测试: 第十章找到的每个位置都应该是真实的标题

        验证: 每个位置所在行不应包含"放置于"、"详见"等引用关键词
        """
        title = "第十章 其他资料"
        positions = matcher._find_full_title_positions(title, test_doc_text)

        false_positive_keywords = ['放置于', '详见', '参见', '置于']

        for pos in positions:
            # 获取位置所在行
            line_start = test_doc_text.rfind('\n', 0, pos) + 1
            line_end = test_doc_text.find('\n', pos)
            if line_end == -1:
                line_end = len(test_doc_text)
            line_text = test_doc_text[line_start:line_end]

            # 验证不包含引用关键词
            for keyword in false_positive_keywords:
                assert keyword not in line_text, \
                    f"位置 {pos} 包含引用关键词 '{keyword}': {repr(line_text[:50])}"

    # ==================== 过滤逻辑测试 ====================

    @pytest.mark.parametrize("line,expected_filter", [
        # 假阳性案例 - 应该被过滤
        ("横道图，应将页面横向布置，放置于第十章其他资料中。", True),
        ("详见第十章其他资料", True),
        ("参见第十章其他资料", True),
        ("人员证件放置于第十章其他资料中", True),

        # 真实标题案例 - 应该被接受
        ("第十章 其他资料", False),
        ("  第十章 其他资料  ", False),
        ("第十章 其他资料（一）", False),
    ])
    def test_false_positive_filtering(self, matcher, line, expected_filter):
        """
        测试: _is_likely_title_position 正确过滤假阳性

        expected_filter: True = 应该被过滤, False = 应该被接受
        """
        title = "第十章 其他资料"
        line_normalized = matcher._normalize_title(line)
        title_normalized = matcher._normalize_title(title)

        # 检查是否匹配
        if title_normalized not in line_normalized:
            pytest.skip("行中不包含标题")

        pos = line_normalized.find(title_normalized)
        is_likely = matcher._is_likely_title_position(line_normalized, pos, title_normalized)

        if expected_filter:
            assert not is_likely, f"应该过滤: {repr(line)}"
        else:
            assert is_likely, f"应该接受: {repr(line)}"

    # ==================== 跨行匹配测试 ====================

    def test_cross_line_matching_validation(self, matcher):
        """
        测试: 跨行匹配时正确验证当前行内容

        场景: 章节号和标题分行显示
        - 当前行: "第十章" (应该只包含章节号)
        - 下一行: "其他资料" (标题正文)
        """
        # 构造跨行文本（模拟真实PDF场景）
        text = "第十章\n其他资料\n\n第十章 其他资料"

        title = "第十章 其他资料"
        positions = matcher._find_full_title_positions(title, text)

        # 应该至少找到1个位置（跨行匹配或单行匹配）
        assert len(positions) >= 1, f"期望至少1个位置，实际{len(positions)}个: {positions}"

        # 验证每个位置都是有效的（不包含"放置于"等引用词）
        for pos in positions:
            context = text[max(0, pos-5):pos+15]
            # 在构造的测试数据中不应有"放置于"
            assert "放置于" not in context, f"找到假阳性位置: {context}"

    # ==================== 回归测试 ====================

    def test_all_standard_chapters(self, matcher, test_doc_text):
        """
        测试: 所有标准章节标题都能被正确找到（在测试文档中存在的章节）

        注: 测试文档可能不包含所有章节，只测试存在的章节
        """
        # 测试文档中实际存在的章节
        existing_chapters = [
            "第一章 编制依据",
            "第二章 工程概况",
            "第三章 施工计划",
            "第四章 施工工艺技术",
            "第十章 其他资料",
        ]

        results = {}
        for title in existing_chapters:
            positions = matcher._find_full_title_positions(title, test_doc_text)
            results[title] = len(positions)

            # 每个章节应该至少找到1个位置（可能是目录或正文）
            assert len(positions) >= 1, f"'{title}' 未找到任何位置"

            # 每个章节不应该超过3个位置（目录+正文+可能的页眉）
            assert len(positions) <= 3, f"'{title}' 找到过多位置: {positions}"

        # 输出统计信息
        print("\n章节查找统计:")
        for title, count in results.items():
            print(f"  {title}: {count}个位置")

    # ==================== 边界情况测试 ====================

    @pytest.mark.parametrize("title,expected_positions", [
        ("第十章 其他资料", [4524, 43321]),  # 已知正确位置
    ])
    def test_specific_position_values(self, matcher, test_doc_text, title, expected_positions):
        """
        测试: 特定标题返回的位置值是否符合预期

        注意: 这个测试依赖于测试文档的稳定性
        """
        positions = matcher._find_full_title_positions(title, test_doc_text)

        # 验证返回的位置包含期望值
        for expected in expected_positions:
            assert expected in positions, \
                f"期望位置 {expected} 未找到，实际位置: {positions}"

    def test_empty_title(self, matcher, test_doc_text):
        """测试: 空标题返回空列表"""
        positions = matcher._find_full_title_positions("", test_doc_text)
        assert positions == []

    def test_nonexistent_title(self, matcher, test_doc_text):
        """测试: 不存在的标题返回空列表"""
        positions = matcher._find_full_title_positions("不存在的章节", test_doc_text)
        assert positions == []


class TestIsLikelyTitlePosition:
    """_is_likely_title_position 方法专项测试"""

    @pytest.fixture
    def matcher(self):
        return TitleMatcher()

    def test_position_at_line_start(self, matcher):
        """测试: 行首位置总是被认为可能是标题"""
        line = "第十章 其他资料"
        pos = 0
        result = matcher._is_likely_title_position(line, pos, "第十章")
        assert result is True

    def test_short_prefix_allowed(self, matcher):
        """测试: 短前缀（<=3字符）被允许"""
        line = "一、第十章 其他资料"
        pos = 3  # "第十章"的位置
        result = matcher._is_likely_title_position(line, pos, "第十章 其他资料")
        assert result is True

    def test_long_prefix_with_common_words_filtered(self, matcher):
        """测试: 长前缀（>5字符）包含常见词汇被过滤"""
        line = "横道图放置于第十章其他资料中"
        pos = 5  # "第十章"的位置
        result = matcher._is_likely_title_position(line, pos, "第十章其他资料")
        assert result is False

    def test_specific_phrases_filtered(self, matcher):
        """测试: 特定短语被过滤"""
        # 构造包含引用词的测试行（使用normalized后的格式）
        test_line = "横道图，应将页面横向布置，放置于第十章 其他资料中。"
        title = "第十章 其他资料"

        line_normalized = matcher._normalize_title(test_line)
        title_normalized = matcher._normalize_title(title)

        # 验证标题在normalized行中
        assert title_normalized in line_normalized, f"标题不在行中: {repr(line_normalized)}"

        pos = line_normalized.find(title_normalized)
        result = matcher._is_likely_title_position(line_normalized, pos, title_normalized)

        # 应该被过滤（因为前缀包含"放置于"等引用词）
        assert result is False, f"应该过滤包含引用词的行: {repr(test_line)}"


# ==================== 主程序入口 ====================

def run_manual_tests():
    """手动运行测试（不使用pytest时）"""
    print("=" * 70)
    print("TitleMatcher 标题位置计算修复 - 手动测试")
    print("=" * 70)

    matcher = TitleMatcher()

    # 加载测试文档
    test_file = 'D:/wx_work/sichuan_luqiao/lu_sgsc_testfile/测试模版-四川路桥专项施工方案框架以及编制说明（2025修订第三版）- v0.2.pdf'
    if not Path(test_file).exists():
        print(f"测试文件不存在: {test_file}")
        return False

    doc = fitz.open(test_file)
    full_text = ""
    for page in doc:
        full_text += page.get_text()
    doc.close()

    print(f"测试文件: {test_file}")
    print(f"全文长度: {len(full_text)} 字符")
    print()

    # 测试1: 第十章假阳性过滤
    print("测试1: 第十章假阳性过滤")
    print("-" * 70)
    title = "第十章 其他资料"
    positions = matcher._find_full_title_positions(title, full_text)

    false_positive_positions = [14328, 39690]
    has_false_positive = any(fp in positions for fp in false_positive_positions)

    print(f"  找到的位置: {positions}")
    print(f"  期望的假阳性位置: {false_positive_positions}")
    print(f"  结果: {'通过 ✓' if not has_false_positive else '失败 ✗'}")
    print()

    # 测试2: 过滤逻辑
    print("测试2: 过滤逻辑验证")
    print("-" * 70)
    test_cases = [
        ("横道图放置于第十章其他资料中", False, "假阳性-放置于"),
        ("第十章 其他资料", True, "真实标题"),
    ]

    all_passed = True
    for line, expected_accept, desc in test_cases:
        line_normalized = matcher._normalize_title(line)
        title_normalized = matcher._normalize_title(title)

        if title_normalized in line_normalized:
            pos = line_normalized.find(title_normalized)
            is_likely = matcher._is_likely_title_position(line_normalized, pos, title_normalized)
            passed = is_likely == expected_accept
            all_passed = all_passed and passed
            status = "通过 ✓" if passed else "失败 ✗"
            print(f"  {status} [{desc}] {repr(line[:30])}")

    print()
    print("=" * 70)
    print(f"最终结论: {'所有测试通过 ✓' if not has_false_positive and all_passed else '部分测试失败 ✗'}")
    print("=" * 70)

    return not has_false_positive and all_passed


if __name__ == "__main__":
    # 如果没有pytest，运行手动测试
    try:
        import pytest
        sys.exit(pytest.main([__file__, "-v"]))
    except ImportError:
        success = run_manual_tests()
        sys.exit(0 if success else 1)
