"""Annotation Validator & Repairer - 标注结果校验与修复.

不依赖 LLM，纯算法实现。

校验规则：
    annotated_text 中 ``{{}}`` 外的文本 + 各 ``{{}}`` 内第 3 段（raw_text）
    按顺序拼接，应当与 original_article 完全相等。

修复策略：
    1. 完全匹配检查：已匹配则直接返回。
    2. difflib 对齐重建：用 SequenceMatcher 把 article 和当前 reconstructed
       的所有"等价块"作为锚点，把每个 segment 边界（包括 ``{{}}`` 外的
       text segment 边界）映射到 article 中对应位置；用 article 对应片段
       替换 segment 内容（text 直接替换，annotation 替换 body 第 3 段）。

       该策略对 ``{{}}`` 外的文本幻觉同样有效：
       - 全角/半角引号混淆（"…" vs "…"）
       - 标点偏差（。 vs .）
       - 漏字、加字、空格变化
       - raw_text 被 LLM 错填为 value
       修复保持最小编辑距离：只修改与原文不一致的 segment。
"""

from __future__ import annotations

import difflib
import logging
import re
from typing import Any

logger = logging.getLogger(__name__)


# ──────────────────────────────────────────────────────────────────────────
# 标注文本解析（与前端 parseAnnotationToken 规则保持一致）
# ──────────────────────────────────────────────────────────────────────────

_ANNOTATION_RE = re.compile(r"\{\{([^{}]*)\}\}")


def parse_annotated_segments(annotated_text: str) -> list[dict[str, Any]]:
    """解析标注文本为 segments 列表.

    Returns:
        交替的 text/annotation segment，每项含::
            - type: "text" | "annotation"
            - content (text) / body (annotation): 原始字符
            - raw_text (annotation): 第 3 段反转义后的原文片段
            - start, end: 在 annotated_text 中的字符位置
    """
    segments: list[dict[str, Any]] = []
    last_end = 0
    for match in _ANNOTATION_RE.finditer(annotated_text):
        if match.start() > last_end:
            segments.append({
                "type": "text",
                "content": annotated_text[last_end:match.start()],
                "start": last_end,
                "end": match.start(),
            })
        body = match.group(1)
        segments.append({
            "type": "annotation",
            "body": body,
            "raw_text": _extract_body_field(body, 2),  # COL3 = raw_text
            "start": match.start(),
            "end": match.end(),
        })
        last_end = match.end()
    if last_end < len(annotated_text):
        segments.append({
            "type": "text",
            "content": annotated_text[last_end:],
            "start": last_end,
            "end": len(annotated_text),
        })
    return segments


def reconstruct_article(annotated_text: str) -> str:
    """从标注文本重建原文：text segments 内容 + annotation 的 raw_text 按序拼接."""
    parts: list[str] = []
    for seg in parse_annotated_segments(annotated_text):
        if seg["type"] == "text":
            parts.append(seg["content"])
        else:
            parts.append(seg["raw_text"])
    return "".join(parts)


# ──────────────────────────────────────────────────────────────────────────
# 校验
# ──────────────────────────────────────────────────────────────────────────

def validate_annotation(
    original_article: str,
    annotated_text: str,
) -> tuple[bool, str]:
    """校验标注文本是否能还原原文.

    Returns:
        (是否一致, 重建后的文本)
    """
    reconstructed = reconstruct_article(annotated_text)
    return reconstructed == original_article, reconstructed


# ──────────────────────────────────────────────────────────────────────────
# 修复
# ──────────────────────────────────────────────────────────────────────────

def repair_annotation(
    original_article: str,
    annotated_text: str,
) -> tuple[str, list[str]]:
    """尝试修复 annotated_text，使其能还原 original_article.

    Args:
        original_article: 原始文章文本（不含标注）
        annotated_text: 含 ``{{}}`` 标注的文本

    Returns:
        (修复后的 annotated_text, 修复日志列表)
        日志为空表示完全匹配，无需修复。
    """
    logs: list[str] = []

    # 策略 1：完全匹配
    reconstructed = reconstruct_article(annotated_text)
    if reconstructed == original_article:
        return annotated_text, logs

    logs.append(
        f"标注与原文不一致：重建长度={len(reconstructed)}，"
        f"原文长度={len(original_article)}"
    )

    # 策略 2：difflib 对齐重建（同时处理 {{}} 外文本幻觉与 raw_text 错填）
    segments = parse_annotated_segments(annotated_text)
    seg_a_ranges = _align_segments_to_article(
        segments, original_article, reconstructed,
    )

    changes = 0
    text_changes = 0
    ann_changes = 0
    new_parts: list[str] = []
    for seg, (a_start, a_end) in zip(segments, seg_a_ranges):
        article_slice = original_article[a_start:a_end]
        old_content = seg["content"] if seg["type"] == "text" else seg["raw_text"]

        if article_slice == old_content:
            # 一致：原样保留
            if seg["type"] == "text":
                new_parts.append(seg["content"])
            else:
                new_parts.append("{{" + seg["body"] + "}}")
            continue

        # 不一致：用 article 对应片段替换
        changes += 1
        if seg["type"] == "text":
            text_changes += 1
            new_parts.append(article_slice)
        else:
            ann_changes += 1
            new_body = _replace_body_field(seg["body"], 2, article_slice)
            new_parts.append("{{" + new_body + "}}")

    repaired = "".join(new_parts)

    # 二次校验
    if reconstruct_article(repaired) == original_article:
        logs.append(
            f"difflib 对齐重建成功：共修改 {changes} 个 segment "
            f"（text {text_changes}，annotation raw_text {ann_changes}）"
        )
        return repaired, logs

    logs.append("difflib 对齐后仍不匹配原文，放弃修复")
    return annotated_text, logs


def _align_segments_to_article(
    segments: list[dict[str, Any]],
    article: str,
    reconstructed: str,
) -> list[tuple[int, int]]:
    """用 difflib 把每个 segment 边界映射到 article 中对应位置.

    算法：
        1. 计算 article 和 reconstructed 的所有 equal 块（matching_blocks）
        2. 对每个 segment 边界（在 reconstructed 中的位置）：
           - 落在 equal 块内：精确映射 a_pos = a_start + (b_pos - b_start)
           - 落在差异区间：映射到前一个 equal 块的 a_end
             （等于后一个 equal 块的 a_start，因为差异区间无匹配）
        3. 每个 segment 的 article 区间 = [左边界的 a 位置, 右边界的 a 位置)
        4. 防御性兜底：第一个 segment 从 0 开始，最后一个到 len(article) 结束，
           保证 article 字符无遗漏。

    Returns:
        每个 segment 的 article 区间列表 [(a_start, a_end), ...]
    """
    # 构建 segment 在 reconstructed 中的边界
    boundaries: list[int] = [0]
    for seg in segments:
        content = seg["content"] if seg["type"] == "text" else seg["raw_text"]
        boundaries.append(boundaries[-1] + len(content))
    # boundaries 有 n+1 个元素；segment i 占据 [boundaries[i], boundaries[i+1])

    # difflib 找出 equal 块
    matcher = difflib.SequenceMatcher(
        a=article, b=reconstructed, autojunk=False,
    )
    matching_blocks = matcher.get_matching_blocks()
    # [(a_idx, b_idx, size), ...]，按 b 升序，最后是 (len_a, len_b, 0)

    def map_b_to_a(b_pos: int) -> int:
        """把 reconstructed 中的位置映射到 article 中的位置.

        边界落在 equal 块内时精确映射；落在差异区时取前一个 equal 块的
        a_end（避免吃掉差异区的字符）。
        """
        prev_a_end = 0
        for a_idx, b_idx, size in matching_blocks:
            if size == 0:
                continue
            block_b_start = b_idx
            block_b_end = b_idx + size
            block_a_start = a_idx
            block_a_end = a_idx + size

            # 用左闭右开区间，避免块右端被错误"吸住"
            if block_b_start <= b_pos < block_b_end:
                # 边界落在 equal 块内：精确映射
                return block_a_start + (b_pos - block_b_start)

            if b_pos < block_b_start:
                # 边界落在差异区：返回前一个 equal 块的 a_end
                # （若差异区两端 a 边界不对齐，差异区字符归给后一个 segment）
                return prev_a_end

            prev_a_end = block_a_end

        return prev_a_end

    # 计算每个 segment 的 a 区间
    seg_a_ranges: list[tuple[int, int]] = []
    for i in range(len(segments)):
        a_start = map_b_to_a(boundaries[i])
        a_end = map_b_to_a(boundaries[i + 1])
        if a_end < a_start:  # 防御
            a_end = a_start
        seg_a_ranges.append((a_start, a_end))

    # 防御性兜底：保证首尾覆盖整个 article
    if seg_a_ranges:
        first_start, first_end = seg_a_ranges[0]
        if first_start > 0:
            seg_a_ranges[0] = (0, first_end)

        last_start, last_end = seg_a_ranges[-1]
        if last_end < len(article):
            seg_a_ranges[-1] = (last_start, len(article))

    return seg_a_ranges


# ──────────────────────────────────────────────────────────────────────────
# {{}} body 字段操作（与 annotator.escape_field / unescape_field 规则一致）
# ──────────────────────────────────────────────────────────────────────────

def _extract_body_field(body: str, field_idx: int) -> str:
    """从 body 中提取第 field_idx 段（0-indexed）并反转义.

    段分隔符为 ``||``（双管道，未转义），单管道必须以 ``\\|`` 转义.
    """
    fields = _split_unescaped(body, "||")
    if field_idx >= len(fields):
        return ""
    return _unescape(fields[field_idx])


def _replace_body_field(body: str, field_idx: int, new_value: str) -> str:
    """替换 body 中第 field_idx 段为 new_value，其他段原样保留.

    其他段的转义序列保持不变；new_value 会被转义后写入。
    段数不足时用空字符串填充至 field_idx。
    """
    fields = _split_unescaped(body, "||")
    while len(fields) <= field_idx:
        fields.append("")
    fields[field_idx] = _escape(new_value)
    return "||".join(fields)


def _split_unescaped(text: str, delimiter: str) -> list[str]:
    """按未转义的 delimiter 分割 text（保留各段内的转义序列）.

    支持 multi-char delimiter（如 ``||``）。遇到 ``\\X`` 时原样保留转义序列.
    """
    result: list[str] = []
    buf: list[str] = []
    i = 0
    delim_len = len(delimiter)
    while i < len(text):
        if text[i] == "\\" and i + 1 < len(text):
            buf.append(text[i])
            buf.append(text[i + 1])
            i += 2
            continue
        if text[i:i + delim_len] == delimiter:
            result.append("".join(buf))
            buf = []
            i += delim_len
            continue
        buf.append(text[i])
        i += 1
    result.append("".join(buf))
    return result


def _unescape(text: str) -> str:
    """反转义：``\\X`` → ``X``."""
    result: list[str] = []
    i = 0
    while i < len(text):
        if text[i] == "\\" and i + 1 < len(text):
            result.append(text[i + 1])
            i += 2
        else:
            result.append(text[i])
            i += 1
    return "".join(result)


def _escape(text: str) -> str:
    """转义：``\\`` → ``\\\\``，``|`` → ``\\|``，``}`` → ``\\}``."""
    return text.replace("\\", "\\\\").replace("|", "\\|").replace("}", "\\}")
