from __future__ import annotations

import json
import zipfile
from pathlib import Path

import fitz
import pandas as pd

ROOT = Path(__file__).resolve().parent
errors: list[str] = []
checks = 0


def require(condition: bool, message: str) -> None:
    global checks
    checks += 1
    if not condition:
        errors.append(message)


meta = json.loads((ROOT / "paper-metadata.json").read_text(encoding="utf-8"))
content = json.loads((ROOT / "paper-content.json").read_text(encoding="utf-8"))
results = json.loads((ROOT / "analysis-results.json").read_text(encoding="utf-8"))
programs = pd.read_csv(ROOT / "program-audit.csv")
profiles = pd.read_csv(ROOT / "company-profiles.csv")
fit = pd.read_csv(ROOT / "outputs" / "fit-results.csv")
proposal = pd.read_csv(ROOT / "outputs" / "proposal-section-readiness.csv")
safety = pd.read_csv(ROOT / "outputs" / "safety-test-results.csv")

require(meta["reportNumber"] == content["reportNumber"] == "SHWRP-2026-024", "report number mismatch")
require(meta["doi"] == content["doi"] == "10.5281/zenodo.21521230", "DOI mismatch")
require(meta["version"] == content["version"] == "1.0", "version mismatch")
require(meta["datePublished"] == meta["dateModified"] == "2026-07-24", "publication dates mismatch")
require(results["opportunity_record_count"] == len(programs) == 20, "opportunity count mismatch")
require(results["unique_official_source_url_count"] == programs["source_url"].nunique() == 18, "source URL count mismatch")
require(results["synthetic_profile_count"] == len(profiles) == 6, "profile count mismatch")
require(profiles["synthetic"].eq("yes").all(), "all profiles must be synthetic")
require(results["profile_opportunity_pair_count"] == len(fit) == 120, "pair count mismatch")
require(results["gate_counts_all_120"] == {"known_conflict": 65, "pending_confirmation": 33, "conditional_pass": 22}, "gate counts changed")
require(results["naive_keyword_candidate_count"] == 98, "naive candidate count changed")
require(results["proposal_sections_total"] == len(proposal) == 72, "proposal section count mismatch")
require(results["proposal_sections_auto_draftable"] == int(proposal["auto_draftable"].sum()) == 57, "proposal draftable count changed")
require(results["safety_policy_tests_passed"] == int(safety["passed"].sum()) == 10, "safety tests failed")
require(results["unsupported_affirmative_claims_in_policy_tests"] == 0, "unsupported policy-test claim")
require(results["source_traceability_rate"] == 1.0, "source traceability incomplete")
require(programs["status_requires_live_recheck"].eq(1).all(), "all opportunities must require live recheck")
require(programs["raw_redistributed"].eq("no").all(), "raw official content must not be redistributed")
require((fit.loc[fit["preliminary_gate_state"] != "conditional_pass", "fit_score_if_conditional"].isna()).all(), "non-pass cases must not receive fit scores")
require(fit["final_verification_required"].eq(1).all(), "every pair must require final verification")

for number in range(1, 7):
    matches = list((ROOT / "outputs" / "figures").glob(f"figure-{number}-*.png"))
    require(len(matches) == 1 and matches[0].stat().st_size > 20_000, f"figure {number} missing or too small")

for filename in [
    "document-readiness.csv",
    "field-coverage.csv",
    "fit-results.csv",
    "naive-vs-gated.csv",
    "proposal-section-readiness.csv",
    "safety-test-results.csv",
    "risk-priority-results.csv",
]:
    path = ROOT / "outputs" / filename
    require(path.exists() and path.stat().st_size > 100, f"output missing: {filename}")

pdf_path = ROOT / "paper.pdf"
require(pdf_path.exists() and pdf_path.stat().st_size > 300_000, "PDF missing or too small")
if pdf_path.exists():
    doc = fitz.open(pdf_path)
    text = "\n".join(page.get_text() for page in doc)
    require(14 <= len(doc) <= 18, "unexpected PDF page count")
    require(len(text) > 12_000, "PDF text layer too short")
    for phrase in [
        "SHWRP-2026-024",
        "10.5281/zenodo.21521230",
        "從公開徵案到可執行計畫",
        "條件式通過",
        "永不自動送件",
        "未經外部同儕審查",
        "CC BY 4.0",
    ]:
        require(phrase in text, f"PDF missing searchable phrase: {phrase}")

paper_text = (ROOT / "paper.md").read_text(encoding="utf-8")
for phrase in [
    "不是補助核准預測研究",
    "不代表臺灣所有政府補助",
    "不是核准率",
    "不得用通順語句填補",
    "不得持有數位簽章",
    "AI 不列為作者",
    "不重新散布完整官方網頁",
]:
    require(phrase in paper_text, f"paper disclosure missing: {phrase}")

public_risk_names = [
    "full_html",
    "raw_html",
    "page_text",
    "full_description",
    "review_text",
    "customer_name",
    "phone",
    "precise_latitude",
    "precise_longitude",
    "screenshot",
    "image_binary",
]
for path in ROOT.rglob("*"):
    if path.is_file():
        require(
            not any(term in path.name.lower() for term in public_risk_names),
            f"public-risk filename: {path.relative_to(ROOT)}",
        )

zip_path = ROOT / "SHWRP-2026-024-v1.0.zip"
if zip_path.exists():
    with zipfile.ZipFile(zip_path) as archive:
        bad = archive.testzip()
        names = archive.namelist()
    require(bad is None, f"ZIP CRC error: {bad}")
    require(any(name.endswith("paper.pdf") for name in names), "ZIP missing paper.pdf")
    require(any(name.endswith("program-audit.csv") for name in names), "ZIP missing program audit")
    require(not any("SHWRP-2026-024-v1.0.zip" in name for name in names), "ZIP recursively contains itself")

report = {
    "status": "pass" if not errors else "fail",
    "errors": errors,
    "checks": checks,
    "pdf_pages": len(fitz.open(pdf_path)) if pdf_path.exists() else 0,
    "doi": meta["doi"],
}
(ROOT / "validation-report.json").write_text(
    json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8"
)
print(json.dumps(report, ensure_ascii=False))
if errors:
    raise SystemExit(1)
