"""字体/字号/复合样式/行距 check。"""

import re

from fastapi_modules.fastapi_leaudit.govdoc_engine.engine.checks import register
from fastapi_modules.fastapi_leaudit.govdoc_engine.engine.checks.base import CheckBase, CheckContext, CheckHit, CheckResult
from fastapi_modules.fastapi_leaudit.govdoc_engine.models import Paragraph, ParagraphStyle


def _font_match(actual: str | None, expect: str) -> bool:
    if not actual:
        return False
    return expect in actual or actual in expect


def _size_match(actual: float | None, expect: float, tol: float = 0.5) -> bool:
    if actual is None:
        return False
    return abs(actual - expect) <= tol


def _style_matches(style: ParagraphStyle, expect: dict) -> bool:
    if "eastasia" in expect and not _font_match(style.font_eastasia, expect["eastasia"]):
        return False
    if "size_pt" in expect and not _size_match(
        style.font_size_pt, float(expect["size_pt"])
    ):
        return False
    if "bold" in expect and bool(style.bold) != bool(expect["bold"]):
        return False
    return True


@register("font")
class FontCheck(CheckBase):
    def run(self, ctx: CheckContext) -> CheckResult:
        expect = ctx.stage.expect or {}
        hits: list[CheckHit] = []
        for p in ctx.paragraphs:
            ok = True
            actual = {
                "font": p.style.font_eastasia,
                "size": p.style.font_size_pt,
            }
            if "eastasia" in expect and not _font_match(p.style.font_eastasia, expect["eastasia"]):
                ok = False
            if "size_pt" in expect and not _size_match(
                p.style.font_size_pt, float(expect["size_pt"])
            ):
                ok = False
            if not ok:
                hits.append(CheckHit(
                    paragraph=p, char_start=0, char_end=len(p.text),
                    actual=actual, expected=expect,
                    message=f"字体或字号不符合（实际 {actual['font']} {actual['size']}pt，期望 {expect}）",
                ))
        return CheckResult(passed=not hits, hits=hits)


@register("style_match")
class StyleMatchCheck(CheckBase):
    def run(self, ctx: CheckContext) -> CheckResult:
        expect = ctx.stage.expect or {}
        hits: list[CheckHit] = []
        for p in ctx.paragraphs:
            ok = True
            actual = {
                "font": p.style.font_eastasia,
                "size": p.style.font_size_pt,
                "bold": p.style.bold,
                "italic": p.style.italic,
                "alignment": p.style.alignment,
            }
            if "eastasia" in expect and not _font_match(p.style.font_eastasia, expect["eastasia"]):
                ok = False
            if "size_pt" in expect and not _size_match(
                p.style.font_size_pt, float(expect["size_pt"])
            ):
                ok = False
            if "bold" in expect and bool(p.style.bold) != bool(expect["bold"]):
                ok = False
            if "alignment" in expect and p.style.alignment != expect["alignment"]:
                ok = False
            if not ok:
                hits.append(CheckHit(
                    paragraph=p, char_start=0, char_end=len(p.text),
                    actual=actual, expected=expect, message="样式不符合",
                ))
        return CheckResult(passed=not hits, hits=hits)


_ATTACHMENT_MARKER_RE = re.compile(r"^\s*(附件[:：]|附件\d+)")


@register("attachment_marker_style")
class AttachmentMarkerStyleCheck(CheckBase):
    """只校验“附件：”或“附件1”等标记本身，不校验后续附件名称。"""

    DEFAULT_EXPECT = {"eastasia": "黑体", "size_pt": 16, "bold": False}

    def run(self, ctx: CheckContext) -> CheckResult:
        expect = ctx.stage.expect or self.DEFAULT_EXPECT
        hits: list[CheckHit] = []
        for p in ctx.paragraphs:
            match = _ATTACHMENT_MARKER_RE.match(p.text)
            if not match:
                continue
            marker_end = match.end(1)
            marker_styles = _marker_run_styles(p, marker_end)
            if not marker_styles:
                marker_styles = [p.style]
            bad_style = next(
                (style for style in marker_styles if not _style_matches(style, expect)),
                None,
            )
            if bad_style is not None:
                hits.append(CheckHit(
                    paragraph=p,
                    char_start=match.start(1),
                    char_end=marker_end,
                    actual={
                        "font": bad_style.font_eastasia,
                        "size": bad_style.font_size_pt,
                        "bold": bad_style.bold,
                    },
                    expected=expect,
                    message="附件标记样式不符合",
                ))
        return CheckResult(passed=not hits, hits=hits)


def _marker_run_styles(p: Paragraph, marker_end: int) -> list[ParagraphStyle]:
    styles: list[ParagraphStyle] = []
    cursor = 0
    for run in p.runs:
        run_start = cursor
        run_end = cursor + len(run.text)
        cursor = run_end
        if run_end <= 0 or run_start >= marker_end:
            continue
        if run.text.strip():
            styles.append(run.style)
    return styles


@register("line_spacing")
class LineSpacingCheck(CheckBase):
    def run(self, ctx: CheckContext) -> CheckResult:
        expect = ctx.stage.expect or {}
        target = float(expect.get("multiple", 1.5))
        tol = float(expect.get("tol", 0.05))
        hits: list[CheckHit] = []
        for p in ctx.paragraphs:
            actual = p.style.line_spacing
            if actual is None or abs(actual - target) > tol:
                hits.append(CheckHit(
                    paragraph=p, char_start=0, char_end=len(p.text),
                    actual={"line_spacing": actual},
                    expected={"line_spacing": target},
                    message=f"行距应为 {target}，实际 {actual}",
                ))
        return CheckResult(passed=not hits, hits=hits)
