# Copyright (C) 2026 NPO NT
# SPDX-License-Identifier: AGPL-3.0-only

from __future__ import annotations

import hashlib
from pathlib import Path
from typing import Any

from pypdf import PdfReader

from .analyzer import analyze_document
from .loader import DocumentLoadError, load_document
from .models import ResidualFinding, VerificationResult
from .ocr import TesseractOcr
from .policy import CRITICAL_RESIDUAL_CATEGORIES


def _sha256_file(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as stream:
        for chunk in iter(lambda: stream.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def _embedded_file_count(reader: PdfReader) -> int:
    try:
        root = reader.trailer["/Root"]
        names = root.get("/Names")
        if names is None:
            return 0
        names = names.get_object()
        embedded = names.get("/EmbeddedFiles")
        if embedded is None:
            return 0
        embedded = embedded.get_object()
        entries = embedded.get("/Names") or []
        return len(entries) // 2
    except Exception:
        return 0


def _has_active_content(reader: PdfReader) -> bool:
    try:
        root: Any = reader.trailer["/Root"]
        if root.get("/OpenAction") is not None or root.get("/AA") is not None:
            return True
        names = root.get("/Names")
        if names is not None and names.get_object().get("/JavaScript") is not None:
            return True
    except Exception:
        return True
    return False


def verify_raster_pdf(
    output_path: str | Path,
    *,
    expected_page_count: int,
    ocr: TesseractOcr | None = None,
) -> VerificationResult:
    path = Path(output_path)
    issues: list[str] = []
    warnings: list[str] = []
    annotation_count = 0
    embedded_count = 0
    text_characters = 0
    residual_count = 0
    residual_findings: list[ResidualFinding] = []

    try:
        reader = PdfReader(path, strict=True)
    except Exception as exc:
        return VerificationResult(
            passed=False,
            issues=[f"Итоговый PDF не открывается строгим парсером: {type(exc).__name__}"],
            warnings=[],
            page_count=0,
            annotation_count=0,
            embedded_file_count=0,
            extracted_text_characters=0,
            residual_finding_count=0,
            output_sha256=_sha256_file(path) if path.exists() else "",
        )

    page_count = len(reader.pages)
    if page_count != expected_page_count:
        issues.append(f"Ожидалось страниц: {expected_page_count}; получено: {page_count}")
    fields = reader.get_fields() or {}
    if fields:
        issues.append("В итоговом PDF обнаружены поля формы")
    for page in reader.pages:
        annotations = page.get("/Annots") or []
        annotation_count += len(annotations)
        extracted = page.extract_text() or ""
        text_characters += len(extracted.strip())
    if annotation_count:
        issues.append("В итоговом PDF обнаружены аннотации")
    if text_characters:
        issues.append("В растровом режиме итоговый PDF не должен содержать извлекаемый текст")
    embedded_count = _embedded_file_count(reader)
    if embedded_count:
        issues.append("В итоговом PDF обнаружены вложенные файлы")
    if _has_active_content(reader):
        issues.append("В итоговом PDF обнаружено активное содержимое")

    try:
        rendered = load_document(path)
    except DocumentLoadError as exc:
        issues.append(f"Не удалось повторно отрисовать итоговый PDF: {exc}")
    else:
        analysis = analyze_document(rendered, ocr=ocr)
        if not analysis.capabilities.complete:
            issues.extend(f"Неполная повторная проверка: {item}" for item in analysis.capabilities.blocking_issues)
        residual = [finding for finding in analysis.findings if finding.category in CRITICAL_RESIDUAL_CATEGORIES]
        residual_count = len(residual)
        for finding in residual:
            page = rendered.pages[finding.page_index]
            residual_findings.append(
                ResidualFinding(
                    page_index=finding.page_index,
                    category=finding.category,
                    box=finding.box,
                    page_width=page.width,
                    page_height=page.height,
                    confidence=finding.confidence,
                )
            )
        if residual:
            categories = sorted({finding.category for finding in residual})
            issues.append("После очистки повторно обнаружены прямые идентификаторы: " + ", ".join(categories))
        warnings.extend(analysis.warnings)

    return VerificationResult(
        passed=not issues,
        issues=issues,
        warnings=list(dict.fromkeys(warnings)),
        page_count=page_count,
        annotation_count=annotation_count,
        embedded_file_count=embedded_count,
        extracted_text_characters=text_characters,
        residual_finding_count=residual_count,
        output_sha256=_sha256_file(path),
        residual_findings=residual_findings,
    )
