//! QF5-D — Target-doc guard expansion (Quality Factory V5).
//!
//! Adds non-redundant regression guards from QF4 / QF5 evidence. All
//! four QF2-F guards (`qf2f_target_doc_guards.rs`) are preserved verbatim;
//! the three guards here are additive and cover orthogonal invariants:
//!
//! 1. **FormCalc representative guard.** `dd416428_dol4n.pdf` is the one
//!    golden that exercises FormCalc (QF1-C: 20 scripts run, 0 errors).
//!    Pins both: FormCalc actually ran (`formcalc_run >= 1`) and produced
//!    no errors (`formcalc_errors == 0`). QF2-F only guards
//!    `js_resolve_failures`, `js_runtime_errors`, `js_oom`, `js_timeouts`
//!    — `formcalc_run` / `formcalc_errors` are entirely uncovered there.
//!
//! 2. **Visual classifier fixture structural integrity.** The committed
//!    baseline fixture `scripts/tests/fixtures/qf2e_classifier_baseline.json`
//!    must be valid JSON with exactly 73 entries. Pins the QF4-F
//!    regression-guard contract: if a future branch accidentally truncates
//!    or corrupts the fixture the guard catches it immediately without
//!    running the full Python test suite. Pure data-shape check, no engine
//!    call, no network.
//!
//! 3. **OF-04 structural guard.** QF4-C discovered that `bda32eb1_pdf_0008`
//!    contains an `<overflow target=…/>` element (XFA §22 routing) and
//!    `e2bb8995_eimm5669e.2` contains a `pageArea` named `"overflowpages"`
//!    with a `contentArea` named `"overflow"` — the two local golden docs
//!    that constitute the OF-04 candidate evidence. This guard asserts the
//!    structural invariant so a golden refresh that replaces either doc with
//!    one that no longer exhibits the OF-04 pattern is caught loudly before
//!    the QF5-A / future OF-04 fix work silently loses its test surface.
//!    Pure data-shape guard, no engine invocation.
//!
//! Non-redundancy matrix (QF2-F guards):
//!
//! | Existing guard | What it checks |
//! |----------------|---------------|
//! | `golden_15_set_resolve_failures_zero` | `js_resolve_failures == 0` on all 15 goldens |
//! | `golden_15_set_page_count_at_oracle_floor` | flattened pages >= oracle on all 15 goldens |
//! | `golden_15_set_no_runtime_errors_or_oom_or_timeouts` | `js_runtime_errors / js_oom / js_timeouts == 0` on all 15 goldens |
//! | `imm_family_goldens_declare_globvalidatepressed_data_item` | `<text name="globValidatePressed"/>` in IMM templates |
//!
//! None of QF2-F's four guards checks `formcalc_run`, `formcalc_errors`,
//! the classifier fixture structure, or the OF-04 template shape.
//!
//! Cross-refs:
//! - `benchmarks/runs/xfa_enterprise_plan/quality_factory_v5/QF5_D_GUARD_EXPANSION_REPORT.md`
//! - `benchmarks/runs/xfa_enterprise_plan/quality_factory_v1/QF1_C_FORMCALC_RESIDUAL_REPORT.md`
//!   (dd416428 is the sole FormCalc-exercising golden: 20 runs, 0 errors)
//! - `benchmarks/runs/xfa_enterprise_plan/quality_factory_v4/QF4_C_OVERFLOW_EVIDENCE_REPORT.md`
//!   (OF-04 discovery: bda32eb1 12 occurrences, e2bb8995 1 occurrence)

use std::path::{Path, PathBuf};

use pdf_xfa::extract::extract_xfa_from_bytes;
use pdf_xfa::flatten_xfa_to_pdf_with_metadata;

/// Path to the 15-doc golden fixture directory, relative to this crate.
fn golden_dir() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR")).join("../xfa-golden-tests/golden")
}

/// Path to the repo root, relative to this crate.
fn repo_root() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("..") // crates/
        .join("..") // repo root
}

// ---------------------------------------------------------------------------
// Guard 1 — FormCalc representative: dd416428_dol4n FormCalc ran + no errors
// ---------------------------------------------------------------------------

/// `dd416428_dol4n.pdf` is the unique golden that exercises FormCalc
/// (QF1-C: `formcalc_run = 20`, `formcalc_errors = 0`). This guard pins
/// both halves of that invariant in CI.
///
/// Specifically guards against two distinct regressions:
/// - `formcalc_run < 1`: FormCalc pipeline silently stopped executing
///   (engine refactor that skips FormCalc, feature-flag change, etc.)
/// - `formcalc_errors > 0`: a FormCalc interpreter regression introduced
///   a new error on previously-passing scripts
///
/// Neither field is guarded by any existing QF2-F test.
#[test]
fn dd416428_dol4n_formcalc_runs_with_zero_errors() {
    let pdf_path = golden_dir().join("dd416428_dol4n.pdf");
    let pdf_bytes = std::fs::read(&pdf_path).expect("read dd416428_dol4n.pdf");

    let (_flat_bytes, metadata) =
        flatten_xfa_to_pdf_with_metadata(&pdf_bytes).expect("flatten dd416428_dol4n.pdf");

    let ds = metadata.dynamic_scripts;

    // FormCalc must have executed at least one script.
    // QF1-C baseline: formcalc_run = 20. Using >= 1 as the floor to
    // accommodate future doc-set trimming while still detecting a full
    // regression to zero (which would mean FormCalc stopped running).
    assert!(
        ds.formcalc_run >= 1,
        "dd416428_dol4n.pdf: formcalc_run = {} (must be >= 1; \
         QF1-C baseline is 20 — if this is 0, FormCalc silently stopped executing)",
        ds.formcalc_run,
    );

    // No FormCalc errors allowed.
    assert_eq!(
        ds.formcalc_errors, 0,
        "dd416428_dol4n.pdf: formcalc_errors = {} (must be 0; \
         QF1-C baseline is 0 — a non-zero value means a FormCalc interpreter regression)",
        ds.formcalc_errors,
    );
}

// ---------------------------------------------------------------------------
// Guard 2 — Visual classifier fixture structural integrity
// ---------------------------------------------------------------------------

/// The committed baseline `scripts/tests/fixtures/qf2e_classifier_baseline.json`
/// must be valid JSON and must contain exactly 73 entries (one per QF2-E
/// doc). Each value must be a string belonging to the known fine-category set.
///
/// This pins the QF4-F regression-guard contract (Python test
/// `scripts/test_xfa_oracle_fault_classifier.py` uses this fixture).
/// If the file is corrupted, truncated, or accidentally modified the
/// Rust guard catches it at `cargo test` time without needing the Python
/// environment.
#[test]
fn qf2e_classifier_baseline_fixture_has_73_valid_entries() {
    let fixture_path = repo_root()
        .join("scripts")
        .join("tests")
        .join("fixtures")
        .join("qf2e_classifier_baseline.json");

    let fixture_text = std::fs::read_to_string(&fixture_path).unwrap_or_else(|e| {
        panic!(
            "read scripts/tests/fixtures/qf2e_classifier_baseline.json: {} \
             (fixture must be committed; check git ls-files scripts/tests/fixtures/)",
            e
        )
    });

    // Validate as JSON: count top-level key-value pairs.
    // We avoid pulling in serde_json; instead we count colon-separated
    // key occurrences in the JSON text. The fixture is a flat object
    // {"filename.pdf": "category", ...} — each entry contributes exactly
    // one `": "` sequence (the colon between key string and value string).
    //
    // Defence: also ensure the document opens and closes as an object `{...}`.
    let trimmed = fixture_text.trim();
    assert!(
        trimmed.starts_with('{') && trimmed.ends_with('}'),
        "qf2e_classifier_baseline.json must be a JSON object ({{...}}); \
         found start={:?} end={:?}",
        &trimmed[..trimmed.len().min(4)],
        &trimmed[trimmed.len().saturating_sub(4)..]
    );

    // Count entries: each entry is a `.pdf": "` pattern (filename key followed
    // by string value). This is precise enough for the committed flat-object shape.
    let entry_count =
        fixture_text.matches(".pdf\": \"").count() + fixture_text.matches(".pdf\":\"").count();

    assert_eq!(
        entry_count, 73,
        "qf2e_classifier_baseline.json must have exactly 73 entries \
         (QF4-F baseline: 73 QF2-E docs); found {} entries",
        entry_count,
    );

    // Validate that all values belong to the known fine-category set.
    // The fixture shape: {"filename.pdf": "fine_category", ...}
    // Extract values by finding quoted strings after `.pdf": "`.
    let known_categories = [
        "antialias_variant",
        "font_metric",
        "element_position",
        "clipping",
        "border_rendering_delta",
        "mixed_rendering",
        // QF4-F additions (may appear in future fixture updates):
        "oracle_artifact",
        "candidate_engine_bug",
    ];

    let mut unknown: Vec<String> = Vec::new();
    // Walk the text looking for `.pdf": "CATEGORY"` or `.pdf":"CATEGORY"` patterns.
    for variant in &["\".pdf\": \"", "\".pdf\":\""] {
        let mut pos = 0;
        while let Some(idx) = fixture_text[pos..].find(variant) {
            let abs = pos + idx + variant.len();
            // Find the closing quote for the value.
            if let Some(close) = fixture_text[abs..].find('"') {
                let category = &fixture_text[abs..abs + close];
                if !known_categories.contains(&category) {
                    unknown.push(category.to_string());
                }
            }
            pos = abs;
        }
    }

    assert!(
        unknown.is_empty(),
        "qf2e_classifier_baseline.json contains unknown fine-category values: {:?}. \
         Allowed: {:?}",
        unknown,
        known_categories,
    );
}

// ---------------------------------------------------------------------------
// Guard 3 — OF-04 structural guard (bda32eb1 + e2bb8995 overflow patterns)
// ---------------------------------------------------------------------------

/// `bda32eb1_pdf_0008.pdf` template must contain an `<overflow target=`
/// element (XFA §22 — subform-level overflow routing). QF4-C discovered
/// 12 such occurrences in this doc; this guard pins the discovery so that
/// a future golden refresh is caught loudly if the replacement no longer
/// carries the OF-04 pattern.
///
/// Pure data-shape guard — no engine invocation. The `extract_xfa_from_bytes`
/// call unpacks the XFA packet; the test then performs a substring search.
#[test]
fn bda32eb1_template_contains_overflow_target_element() {
    let pdf_path = golden_dir().join("bda32eb1_pdf_0008.pdf");
    let pdf_bytes = std::fs::read(&pdf_path).expect("read bda32eb1_pdf_0008.pdf");

    let packets =
        extract_xfa_from_bytes(pdf_bytes).expect("extract XFA packets from bda32eb1_pdf_0008.pdf");

    let template = packets
        .template()
        .expect("bda32eb1_pdf_0008.pdf must have a template packet");

    // Assert that the template carries at least one `<overflow target=` element.
    // QF4-C baseline: 12 occurrences. Using contains() (not count-based) to
    // survive minor template edits that might collapse duplicates while
    // preserving the OF-04 pattern.
    assert!(
        template.contains("<overflow target="),
        "bda32eb1_pdf_0008.pdf: template does not contain '<overflow target='. \
         QF4-C found 12 occurrences (XFA §22 subform-level overflow routing). \
         If this golden was refreshed, verify that the replacement still \
         exercises the OF-04 pattern before updating or removing this guard. \
         (Cross-ref: benchmarks/runs/.../quality_factory_v4/QF4_C_OVERFLOW_EVIDENCE_REPORT.md)"
    );
}

/// `e2bb8995_eimm5669e.2.pdf` template must contain a `pageArea` element
/// named `"overflowpages"` — the named target page area that receives
/// overflow content in this doc (XFA §22 routing semantics). QF4-C flagged
/// this doc as carrying 1 `<overflow target=` occurrence with this page
/// area as the destination.
///
/// Pure data-shape guard — no engine invocation.
#[test]
fn e2bb8995_template_contains_overflow_target_pagearea() {
    let pdf_path = golden_dir().join("e2bb8995_eimm5669e.2.pdf");
    let pdf_bytes = std::fs::read(&pdf_path).expect("read e2bb8995_eimm5669e.2.pdf");

    let packets = extract_xfa_from_bytes(pdf_bytes)
        .expect("extract XFA packets from e2bb8995_eimm5669e.2.pdf");

    let template = packets
        .template()
        .expect("e2bb8995_eimm5669e.2.pdf must have a template packet");

    // Assert that the template carries the named overflow target page area.
    // This is the structural proof that e2bb8995 is an OF-04 candidate:
    // it has a dedicated overflow-target page area in its template.
    assert!(
        template.contains("pageArea name=\"overflowpages\"")
            || template.contains("pageArea name='overflowpages'"),
        "e2bb8995_eimm5669e.2.pdf: template does not contain a pageArea named \
         'overflowpages'. QF4-C identified this as the overflow target page area \
         (XFA §22 routing). If this golden was refreshed verify the replacement \
         still carries the OF-04 pattern before updating this guard. \
         (Cross-ref: benchmarks/runs/.../quality_factory_v4/QF4_C_OVERFLOW_EVIDENCE_REPORT.md)"
    );
}
