"""렌더된 PNG가 silent corruption인지 검증.

tesseract 없어도 픽셀 기반으로 강제 검증.
OCR 미가용 시 mode="PIXEL-ONLY" 명시 — silent skip 절대 금지.

IDS Phase 1 task-2401 — silent corruption 검증.
"""

from __future__ import annotations

from pathlib import Path
from typing import Any


def verify_png(
    png_path: Path,
    *,
    expected_korean: list[str],
    html_source: str | None = None,
    require_ocr: bool = False,
) -> dict[str, Any]:
    """PNG가 silent corruption인지 검증.

    Args:
        png_path: 검증할 PNG 파일 경로.
        expected_korean: 기대 한글 문자열 목록 (OCR/HTML string match용).
        html_source: HTML 소스 문자열 (제공 시 string match 체크).
        require_ocr: True면 OCR 미가용 시 pass=False 처리.

    Returns:
        {
            "pass": bool,
            "checks": {
                "file_size_ok": bool,         # >= 10KB
                "color_diversity_ok": bool,   # unique 색상 >= 10
                "blank_ratio_ok": bool,       # 단일 색상 <= 97%
                "tofu_glyph_clear": bool,     # tofu width 분포 검출
                "html_string_match": bool | None,  # html_source 제공 시
                "ocr_pass": bool | None,      # pytesseract 가용 시
            },
            "metrics": {
                "file_size_kb": float,
                "unique_colors": int,
                "dominant_color_ratio": float,
                "ocr_text": str | None,
                "ocr_match_ratio": float | None,
                "tofu_score": float,          # 0~1 (높을수록 tofu 의심)
            },
            "mode": "PIXEL+OCR" | "PIXEL-ONLY",
            "errors": list[str],
        }
    """
    from PIL import Image  # type: ignore[import-not-found]

    png_path = Path(png_path)
    errors: list[str] = []
    checks: dict[str, bool | None] = {
        "file_size_ok": False,
        "color_diversity_ok": False,
        "blank_ratio_ok": False,
        "tofu_glyph_clear": False,
        "html_string_match": None,
        "ocr_pass": None,
    }
    metrics: dict[str, Any] = {
        "file_size_kb": 0.0,
        "unique_colors": 0,
        "dominant_color_ratio": 1.0,
        "ocr_text": None,
        "ocr_match_ratio": None,
        "tofu_score": 0.0,
    }

    # ── 파일 존재 확인 ──────────────────────────────────────────────────────
    if not png_path.exists():
        errors.append(f"PNG file not found: {png_path}")
        return {
            "pass": False,
            "checks": checks,
            "metrics": metrics,
            "mode": "PIXEL-ONLY",
            "errors": errors,
        }

    # ── 1. 파일 크기 체크 (≥ 10KB) ─────────────────────────────────────────
    file_size_bytes = png_path.stat().st_size
    file_size_kb = file_size_bytes / 1024.0
    metrics["file_size_kb"] = round(file_size_kb, 2)

    if file_size_kb >= 10.0:
        checks["file_size_ok"] = True
    else:
        checks["file_size_ok"] = False
        errors.append(
            f"file_size_ok FAIL: {file_size_kb:.1f}KB < 10KB — "
            "likely placeholder or blank PNG"
        )

    # ── PIL 이미지 열기 ─────────────────────────────────────────────────────
    try:
        img = Image.open(png_path).convert("RGB")
    except Exception as exc:
        errors.append(f"PIL cannot open image: {exc}")
        return {
            "pass": False,
            "checks": checks,
            "metrics": metrics,
            "mode": "PIXEL-ONLY",
            "errors": errors,
        }

    img_width, img_height = img.size

    # ── 2. 색상 다양성 체크 (unique 색상 ≥ 10) ──────────────────────────────
    color_list = img.getcolors(maxcolors=2**24)
    if color_list is None:
        # 색상 수가 2^24 초과 → 매우 다양함
        unique_colors = 2**24
    else:
        unique_colors = len(color_list)

    metrics["unique_colors"] = unique_colors

    if unique_colors >= 10:
        checks["color_diversity_ok"] = True
    else:
        checks["color_diversity_ok"] = False
        errors.append(
            f"color_diversity_ok FAIL: unique_colors={unique_colors} < 10 — "
            "image likely blank or single-color"
        )

    # ── 3. 빈 화면 비율 체크 (단일 색상 ≤ 97%) ──────────────────────────────
    # 카드뉴스 디자인은 배경색이 화면의 80~95%를 차지하는 것이 정상.
    # 실제 blank/placeholder 이미지(흰색/검정 단색)는 file_size_ok + color_diversity_ok에서 이미 검출됨.
    # 97% 임계값: 거의 완전 단색 이미지만 차단 (solid background 카드 false positive 방지).
    dominant_color_ratio = 1.0
    if color_list is not None and len(color_list) > 0:
        # (count, color) 형태
        total_pixels = img_width * img_height
        max_count = max(count for count, _ in color_list)
        dominant_color_ratio = max_count / total_pixels if total_pixels > 0 else 1.0
    elif color_list is None:
        # 색상 매우 다양 → 단일 색상 비율 낮음
        dominant_color_ratio = 0.0

    metrics["dominant_color_ratio"] = round(dominant_color_ratio, 4)

    if dominant_color_ratio <= 0.97:
        checks["blank_ratio_ok"] = True
    else:
        checks["blank_ratio_ok"] = False
        errors.append(
            f"blank_ratio_ok FAIL: dominant_color_ratio={dominant_color_ratio:.2%} > 97% — "
            "image is nearly blank (solid single color)"
        )

    # ── 4. Tofu glyph 검출 ──────────────────────────────────────────────────
    # 이미지를 그레이스케일로 변환 후 이진화
    # 어두운 픽셀 클러스터(텍스트 영역)에서 수평 width 분산이 매우 낮으면 tofu 의심
    tofu_score = _compute_tofu_score(img)
    metrics["tofu_score"] = round(tofu_score, 4)

    if tofu_score < 0.7:
        checks["tofu_glyph_clear"] = True
    else:
        checks["tofu_glyph_clear"] = False
        errors.append(
            f"tofu_glyph_clear FAIL: tofu_score={tofu_score:.3f} >= 0.7 — "
            "possible tofu (□□□) glyph rendering detected"
        )

    # ── 5. HTML string match ─────────────────────────────────────────────────
    if html_source is not None:
        matched_count = sum(1 for kw in expected_korean if kw in html_source)
        total = len(expected_korean)
        all_match = (matched_count == total) if total > 0 else True
        checks["html_string_match"] = all_match
        if not all_match:
            missing = [kw for kw in expected_korean if kw not in html_source]
            errors.append(
                f"html_string_match FAIL: {matched_count}/{total} matched. "
                f"Missing: {missing}"
            )

    # ── 6. OCR (pytesseract optional) ────────────────────────────────────────
    mode = "PIXEL-ONLY"
    ocr_available = False

    try:
        import pytesseract  # type: ignore[import-not-found]
        ocr_available = True
    except ImportError:
        pass

    if ocr_available:
        mode = "PIXEL+OCR"
        try:
            ocr_text: str = pytesseract.image_to_string(img, lang="kor")  # type: ignore[possibly-undefined]
            metrics["ocr_text"] = ocr_text.strip()

            if expected_korean:
                matched_ocr = sum(1 for kw in expected_korean if kw in ocr_text)
                ocr_match_ratio = matched_ocr / len(expected_korean)
            else:
                ocr_match_ratio = 1.0

            metrics["ocr_match_ratio"] = round(ocr_match_ratio, 4)
            ocr_pass = ocr_match_ratio >= 0.5
            checks["ocr_pass"] = ocr_pass

            if not ocr_pass:
                errors.append(
                    f"ocr_pass FAIL: ocr_match_ratio={ocr_match_ratio:.2%} < 50%. "
                    f"OCR text snippet: {ocr_text[:200]!r}"
                )
        except Exception as exc:
            checks["ocr_pass"] = False
            errors.append(f"ocr_pass ERROR: pytesseract failed — {exc}")

    elif require_ocr:
        errors.append(
            "ocr_pass FAIL: require_ocr=True but pytesseract not available — "
            "install pytesseract + tesseract-ocr"
        )
        checks["ocr_pass"] = False
        mode = "PIXEL-ONLY"

    else:
        # OCR 미가용, require_ocr=False → PIXEL-ONLY 명시 (silent skip 금지)
        mode = "PIXEL-ONLY"
        # errors에 명시적으로 기록 (warning level)
        errors.append(
            "INFO: pytesseract not available — running PIXEL-ONLY mode. "
            "Install pytesseract for OCR-based Korean text validation."
        )

    # ── 종합 pass 판정 ───────────────────────────────────────────────────────
    # file_size_ok, color_diversity_ok, blank_ratio_ok, tofu_glyph_clear 필수
    # html_string_match: html_source 제공 시 필수
    # ocr_pass: require_ocr=True 또는 OCR 가용 시 필수
    mandatory_keys = ["file_size_ok", "color_diversity_ok", "blank_ratio_ok", "tofu_glyph_clear"]
    if html_source is not None:
        mandatory_keys.append("html_string_match")
    if require_ocr or ocr_available:
        mandatory_keys.append("ocr_pass")

    overall_pass = all(
        checks.get(k) is True for k in mandatory_keys
    )

    return {
        "pass": overall_pass,
        "checks": dict(checks),
        "metrics": dict(metrics),
        "mode": mode,
        "errors": errors,
    }


def _compute_tofu_score(img: Any) -> float:
    """이미지에서 tofu glyph 점수(0~1)를 계산.

    알고리즘:
    1. 그레이스케일 변환 + 이진화 (어두운 픽셀 = 텍스트 후보)
    2. 각 행(row)에서 연속된 dark pixel 클러스터 width 수집
    3. 클러스터 width의 표준편차가 매우 낮으면 tofu 의심
       - tofu는 동일한 width의 □ 박스가 반복되는 특성
       - 정상 한글은 자/모 조합으로 width 변동이 큼

    Returns:
        0.0 (정상) ~ 1.0 (tofu 강의심)
    """
    import statistics

    gray = img.convert("L")
    width, height = gray.size

    # 이진화 임계값: 128 (어두운 픽셀 = True)
    pixels = gray.load()
    if pixels is None:
        return 0.0

    # 샘플링: 최대 200행 검사 (성능)
    row_step = max(1, height // 200)
    all_cluster_widths: list[int] = []

    for y in range(0, height, row_step):
        in_cluster = False
        cluster_width = 0

        for x in range(width):
            px_val = pixels[x, y]
            is_dark = (px_val < 128)

            if is_dark:
                in_cluster = True
                cluster_width += 1
            else:
                if in_cluster and cluster_width > 0:
                    all_cluster_widths.append(cluster_width)
                in_cluster = False
                cluster_width = 0

        # 행 끝 클러스터
        if in_cluster and cluster_width > 0:
            all_cluster_widths.append(cluster_width)

    if len(all_cluster_widths) < 5:
        # 클러스터가 거의 없음 → 텍스트 없는 이미지 (blank에 가까움)
        # blank 이미지는 tofu 판정보다 blank_ratio_ok에서 잡음
        return 0.0

    # width 표준편차 계산
    try:
        stdev = statistics.stdev(all_cluster_widths)
        mean_w = statistics.mean(all_cluster_widths)
    except statistics.StatisticsError:
        return 0.0

    if mean_w <= 0:
        return 0.0

    # coefficient of variation (변동계수)
    cv = stdev / mean_w

    # tofu 판정:
    # - CV < 0.15 (변동 매우 적음) + 클러스터 수 많음 → tofu 강의심
    # - CV >= 0.3 → 정상 글리프 다양성
    if cv < 0.05:
        tofu_score = 0.95
    elif cv < 0.10:
        tofu_score = 0.85
    elif cv < 0.15:
        tofu_score = 0.70
    elif cv < 0.20:
        tofu_score = 0.50
    elif cv < 0.30:
        tofu_score = 0.25
    else:
        tofu_score = 0.05

    # 클러스터 수가 적으면 신뢰도 낮춤
    confidence = min(1.0, len(all_cluster_widths) / 50.0)
    return tofu_score * confidence
