"""Annotation Repair Test - validation/repair of annotated text vs original article.

Coverage:
1. Already valid (no repair needed)
2. raw_text wrongly filled with value (typical LLM error) -> rebuild
3. raw_text spelling deviation -> rebuild via difflib alignment
4. Body contains escape chars (|, }, \\) -> escape rules preserved
5. No text segment (all annotation) -> difflib can still repair
6. Insufficient body field count -> auto-fill and replace
7. Annotation at tail (no following text anchor)
8. Annotation at head (no preceding text anchor)
9. Quote hallucination: full-width "" <-> half-width "" in outer text
10. Punctuation hallucination: Chinese 。 <-> English . in outer text
11. Missing char in outer text (typical LLM omission)
12. Mixed errors: outer text + raw_text both deviate

Usage:
    python test_annotation_repair.py
"""

from __future__ import annotations

import sys
from pathlib import Path

# Normalize Windows console to UTF-8 to avoid Chinese garbled output
if sys.platform == "win32":
    try:
        sys.stdout.reconfigure(encoding="utf-8")  # type: ignore[attr-defined]
        sys.stderr.reconfigure(encoding="utf-8")  # type: ignore[attr-defined]
    except Exception:
        pass

# Make `src` importable regardless of CWD
sys.path.insert(0, str(Path(__file__).parent))

from src.annotation_repair import (  # noqa: E402
    parse_annotated_segments,
    repair_annotation,
    validate_annotation,
)


def _ok(label: str, repaired: str | None = None) -> None:
    """打印 [OK] 标记；若提供了 repaired 则附带修复后文本。"""
    print(f"[OK] {label}")
    if repaired is not None:
        print(f"     修复后: {repaired}")


# --- Scene 1: already valid --------------------------------------------------
def test_already_valid():
    article = "2026年6月1日晚8点，刘阳作为总策划。"
    annotated = (
        "{{PDJH.JBXX.PDKSSJ||2026-06-01T20:00:00.000||2026年6月1日晚8点||"
        "日期范围||ISO→中文}}，{{PDJH.RYXX.ZCH.XM||刘阳||刘阳||2~4字||不作转换}}"
        "作为总策划。"
    )
    is_valid, _ = validate_annotation(article, annotated)
    assert is_valid, "fully-matched annotation should pass validation"

    repaired, logs = repair_annotation(article, annotated)
    assert repaired == annotated, "matched annotation must not be modified"
    assert logs == [], "no logs when already valid"
    _ok("scene1: already valid")


# --- Scene 2: raw_text wrongly filled with value (typical LLM error) ---------
def test_raw_text_wrong_as_value():
    article = "2026年6月1日晚8点，刘阳作为总策划。"
    bad_annotated = (
        "{{PDJH.JBXX.PDKSSJ||2026-06-01T20:00:00.000||2026-06-01T20:00:00.000||"
        "日期范围||ISO→中文}}，{{PDJH.RYXX.ZCH.XM||刘阳||刘阳||2~4字||不作转换}}"
        "作为总策划。"
    )
    is_valid, _ = validate_annotation(article, bad_annotated)
    assert not is_valid, "wrongly-filled annotation should fail validation"

    repaired, logs = repair_annotation(article, bad_annotated)
    is_valid_after, _ = validate_annotation(article, repaired)
    assert is_valid_after, f"should pass after repair, logs: {logs}"

    segs = parse_annotated_segments(repaired)
    ann = next(s for s in segs if s["type"] == "annotation")
    assert ann["raw_text"] == "2026年6月1日晚8点", (
        f"raw_text should be rebuilt from article, got: {ann['raw_text']!r}"
    )
    _ok("scene2: raw_text wrongly filled with value repaired", repaired)


# --- Scene 3: multiple annotations with spelling deviation -------------------
def test_raw_text_partial_deviation():
    article = "活动预算为3000元，已支付2000元。"
    bad_annotated = (
        "活动预算为{{PDJH.JBXX.PDZYS||3000.0||三千||数值||不做转换}}元，"
        "已支付{{PDJH.JBXX.YZF||2000.0||两千||数值||不做转换}}元。"
    )
    repaired, logs = repair_annotation(article, bad_annotated)
    is_valid, _ = validate_annotation(article, repaired)
    assert is_valid, f"should pass after repair, logs: {logs}"

    segs = parse_annotated_segments(repaired)
    anns = [s for s in segs if s["type"] == "annotation"]
    assert anns[0]["raw_text"] == "3000", f"first raw_text expected '3000', got: {anns[0]['raw_text']!r}"
    assert anns[1]["raw_text"] == "2000", f"second raw_text expected '2000', got: {anns[1]['raw_text']!r}"
    _ok("scene3: multiple annotations with deviation repaired", repaired)


# --- Scene 4: body contains escape chars -------------------------------------
def test_body_with_escape_chars():
    # Original article contains "|", must be escaped as "\|" in body
    article = "a|b|c 结束"
    # Python literal "\\|" represents the actual 2-char sequence "\|"
    annotated = "{{P||v||a\\|b\\|c||约束||规则1\\|规则2}} 结束"
    is_valid, _ = validate_annotation(article, annotated)
    assert is_valid, "annotated text with escape chars should match article"

    repaired, logs = repair_annotation(article, annotated)
    assert logs == [], "no repair should be triggered when already valid"
    assert repaired == annotated
    _ok("scene4: escape chars preserved")


# --- Scene 5: no text segment (difflib can still repair) --------------------
def test_no_text_segment_can_repair():
    """新算法用 difflib 对齐，无需 text segment 作为锚点也能修复."""
    article = "刘阳"
    bad_annotated = "{{P||刘阳||李四||约束||规则}}"  # raw_text wrongly set to "李四"
    is_valid, _ = validate_annotation(article, bad_annotated)
    assert not is_valid

    repaired, logs = repair_annotation(article, bad_annotated)
    is_valid_after, _ = validate_annotation(article, repaired)
    assert is_valid_after, (
        f"difflib 应能修复纯标注文本，logs: {logs}"
    )

    segs = parse_annotated_segments(repaired)
    ann = next(s for s in segs if s["type"] == "annotation")
    assert ann["raw_text"] == "刘阳", (
        f"raw_text 应被重建为 '刘阳'，实际：{ann['raw_text']!r}"
    )
    _ok("scene5: no text segment - repaired by difflib alignment", repaired)


# --- Scene 6: insufficient body field count ----------------------------------
def test_body_field_count_insufficient():
    article = "张三参加"
    # Truncated body: only path||value
    bad_annotated = "{{P||张三}}参加"
    is_valid, _ = validate_annotation(article, bad_annotated)
    assert not is_valid, "truncated body should fail validation"

    repaired, logs = repair_annotation(article, bad_annotated)
    is_valid_after, _ = validate_annotation(article, repaired)
    assert is_valid_after, f"should auto-fill and repair, logs: {logs}"
    _ok("scene6: insufficient body field count repaired", repaired)


# --- Scene 7: annotation at tail (no following text anchor) ------------------
def test_annotation_at_tail():
    article = "活动结束于2026年6月1日"
    bad_annotated = "活动结束于{{PDJH.JBXX.JSSJ||2026-06-01||结束时间||约束||规则}}"
    is_valid, _ = validate_annotation(article, bad_annotated)
    assert not is_valid

    repaired, logs = repair_annotation(article, bad_annotated)
    is_valid_after, _ = validate_annotation(article, repaired)
    assert is_valid_after, f"tail annotation should repair, logs: {logs}"

    segs = parse_annotated_segments(repaired)
    ann = next(s for s in segs if s["type"] == "annotation")
    assert ann["raw_text"] == "2026年6月1日", (
        f"tail raw_text expected '2026年6月1日', got: {ann['raw_text']!r}"
    )
    _ok("scene7: tail annotation repaired", repaired)


# --- Scene 8: annotation at head (no preceding text anchor) ------------------
def test_annotation_at_head():
    article = "2026年6月1日活动开始"
    bad_annotated = "{{PDJH.JBXX.KSSJ||2026-06-01||错误||约束||规则}}活动开始"
    repaired, logs = repair_annotation(article, bad_annotated)
    is_valid, _ = validate_annotation(article, repaired)
    assert is_valid, f"head annotation should repair, logs: {logs}"
    _ok("scene8: head annotation repaired", repaired)


# --- Scene 9: quote hallucination in outer text (user's real case) -----------
def test_quote_hallucination_in_outer_text():
    """LLM 把中文双引号 \u201c\u201d 改成英文双引号 "（或反向）.

    这是用户实际遇到的失败案例：text segment 在原文中找不到，
    旧版 text-anchored 算法会直接报错放弃。difflib 应能修复。
    """
    article = '指挥员仍然感觉意犹未尽：\u201c每次训练，都是一次全新挑战！\u201d'
    # 把中文双引号 \u201c\u201d 改为英文双引号 "（LLM 典型幻觉）
    bad_annotated = '指挥员仍然感觉意犹未尽："每次训练，都是一次全新挑战！"'
    assert article != bad_annotated, "测试前置：两段文本确实不同"

    repaired, logs = repair_annotation(article, bad_annotated)
    is_valid, _ = validate_annotation(article, repaired)
    assert is_valid, f"引号幻觉应被修复，logs: {logs}"
    assert repaired == article, "修复后 text segment 应与原文一致"
    _ok("scene9: quote hallucination in outer text repaired", repaired)


# --- Scene 10: punctuation hallucination in outer text -----------------------
def test_punctuation_hallucination_in_outer_text():
    """LLM 把中文句号 '。' 改成英文句点 '.'，或类似标点偏差."""
    article = "训练结束。队伍带回。"
    bad_annotated = "训练结束.队伍带回."
    assert article != bad_annotated

    repaired, logs = repair_annotation(article, bad_annotated)
    is_valid, _ = validate_annotation(article, repaired)
    assert is_valid, f"标点幻觉应被修复，logs: {logs}"
    assert repaired == article
    _ok("scene10: punctuation hallucination in outer text repaired", repaired)


# --- Scene 11: missing char in outer text (LLM omission) --------------------
def test_missing_char_in_outer_text():
    """LLM 漏字：text segment 比原文少一个字."""
    article = "本次训练取得了圆满成功，全体官兵表现优秀。"
    # 漏掉 "全体" 中的 "体" 字
    bad_annotated = "本次训练取得了圆满成功，全官兵表现优秀。"
    assert article != bad_annotated

    repaired, logs = repair_annotation(article, bad_annotated)
    is_valid, _ = validate_annotation(article, repaired)
    assert is_valid, f"漏字应被修复，logs: {logs}"
    assert repaired == article
    _ok("scene11: missing char in outer text repaired", repaired)


# --- Scene 12: mixed errors (outer text + raw_text both deviate) -------------
def test_mixed_outer_and_inner_errors():
    """同时存在两种幻觉：{{}} 外引号偏差 + {{}} 内 raw_text 写成 value."""
    article = "2026年6月1日\u2014\u2014指挥员刘阳宣布：\u201c训练开始！\u201d"
    bad_annotated = (
        "{{PDJH.JBXX.RQ||2026-06-01||2026-06-01||日期||ISO}}\u2014\u2014"  # raw_text 错填为 value
        "指挥员刘阳宣布：\u201c训练开始！\u201d"  # 中文引号被改成英文引号
    )

    repaired, logs = repair_annotation(article, bad_annotated)
    is_valid, _ = validate_annotation(article, repaired)
    assert is_valid, f"混合错误应被修复，logs: {logs}"

    # 校验：raw_text 应被重建为 '2026年6月1日'
    segs = parse_annotated_segments(repaired)
    ann = next(s for s in segs if s["type"] == "annotation")
    assert ann["raw_text"] == "2026年6月1日", (
        f"raw_text 应被重建为 '2026年6月1日'，实际：{ann['raw_text']!r}"
    )
    _ok("scene12: mixed outer text + raw_text errors repaired", repaired)


def main():
    tests = [
        test_already_valid,
        test_raw_text_wrong_as_value,
        test_raw_text_partial_deviation,
        test_body_with_escape_chars,
        test_no_text_segment_can_repair,
        test_body_field_count_insufficient,
        test_annotation_at_tail,
        test_annotation_at_head,
        test_quote_hallucination_in_outer_text,
        test_punctuation_hallucination_in_outer_text,
        test_missing_char_in_outer_text,
        test_mixed_outer_and_inner_errors,
    ]
    failed = 0
    for test in tests:
        try:
            test()
        except AssertionError as e:
            failed += 1
            print(f"[FAIL] {test.__name__}: {e}")
        except Exception as e:  # noqa: BLE001
            failed += 1
            print(f"[ERROR] {test.__name__}: {type(e).__name__}: {e}")

    print()
    total = len(tests)
    print(f"result: {total - failed}/{total} passed")
    sys.exit(0 if failed == 0 else 1)


if __name__ == "__main__":
    main()
