"""Runner-level text-layer flow: excerpt into verify scopes, hi-DPI crop
replacement with full-page fallback, and coverage-gap findings."""

import pytest

fitz = pytest.importorskip("pymupdf")

import backend.agents.runner as runner_mod
from backend.agents.base import AgentResult
from backend.agents.runner import run_agent_pipeline

PAGE_TEXT = "(5) 2X6 STUD PACK AT BEARING"


def _make_pdf(path):
    doc = fitz.open()
    page = doc.new_page(width=612, height=792)
    page.insert_text((72, 72), PAGE_TEXT, fontsize=11)
    doc.save(str(path))
    doc.close()
    return str(path)


def _finding(sheets, evidence_text):
    return {
        "issue_id": "C1", "severity": "critical", "confidence": "high",
        "source_stage": "constructability", "sheets": sheets,
        "description": "stud pack conflict",
        "evidence": [{"sheet": sheets[0], "source_text": evidence_text}],
    }


def _stub_agent(artifacts):
    return lambda usage: type("S", (), {
        "name": "stub",
        "run": lambda self, scope: AgentResult(
            scope_id=scope.scope_id, artifacts=list(artifacts)),
    })()


def _patch_pipeline(monkeypatch, finding, verify_sink):
    monkeypatch.setattr(
        runner_mod, "convert_pdf_to_images",
        lambda path: [{"page_number": 1, "base64": "QUJD"}])
    monkeypatch.setattr(runner_mod, "SheetExtractorAgent", _stub_agent([
        {"sheet_number": "S401", "page_number": 1, "level": "roof",
         "discipline": "S", "assertions": [
             {"text": "(5) 2X6 STUD PACK", "object_type": "framing"},
             {"text": "HSS16X4 beam", "object_type": "framing"},
         ]},
    ]))
    monkeypatch.setattr(runner_mod, "SheetIndexAgent", _stub_agent([{}]))
    monkeypatch.setattr(runner_mod, "JurisdictionAgent", _stub_agent([{}]))
    monkeypatch.setattr(runner_mod, "LinkerAgent", _stub_agent([
        {"key": "c1", "location": "roof beam pocket", "assertions": []},
    ]))
    monkeypatch.setattr(runner_mod, "ConflictCriticAgent", _stub_agent([]))
    monkeypatch.setattr(runner_mod, "CodeAgent", _stub_agent([]))
    monkeypatch.setattr(runner_mod, "ConstructabilityAgent",
                        _stub_agent([finding]))
    monkeypatch.setattr(runner_mod, "CompletenessAgent", _stub_agent([]))
    monkeypatch.setattr(
        runner_mod, "BrainAgent",
        lambda usage: type("B", (), {
            "run": lambda self, findings, sheet_index, jurisdiction:
                (list(findings), []),
            "plan_clarifications": lambda self, prioritized: []})())

    class _RecordingVerifier:
        name = "verify"

        def __init__(self, usage):
            pass

        def run(self, scope):
            verify_sink.append(scope.payload)
            return AgentResult(scope_id=scope.scope_id, artifacts=[{
                "finding_index": scope.payload["finding_index"],
                "status": "confirmed",
                "verdicts": [],
            }])

    monkeypatch.setattr(runner_mod, "EvidenceVerifierAgent",
                        lambda usage: _RecordingVerifier(usage))


def test_verify_scope_carries_text_excerpt_and_crop(monkeypatch, tmp_path):
    """Evidence text matches the page text layer -> excerpt present and the
    full-page image is replaced by a hi-DPI crop."""
    sink = []
    _patch_pipeline(monkeypatch,
                    _finding(["S401"], "(5) 2X6 STUD PACK AT BEARING"), sink)
    pdf = _make_pdf(tmp_path / "set.pdf")
    run_agent_pipeline(pdf, out_dir=str(tmp_path), require_review=False)
    assert len(sink) == 1
    payload = sink[0]
    assert "2X6 STUD PACK" in payload["text_layer_excerpt"]
    assert payload["images_b64"], "crop must never drop all images"
    assert payload["images_b64"][0] != "QUJD", "expected crop, not full page"


def test_verify_scope_falls_back_to_full_page(monkeypatch, tmp_path):
    """Evidence text not in the text layer -> keep the full-page image."""
    sink = []
    _patch_pipeline(monkeypatch,
                    _finding(["S401"], "PENTHOUSE EXHAUST FAN EF-9"), sink)
    pdf = _make_pdf(tmp_path / "set.pdf")
    run_agent_pipeline(pdf, out_dir=str(tmp_path), require_review=False)
    assert len(sink) == 1
    assert sink[0]["images_b64"] == ["QUJD"]


def test_coverage_gap_becomes_gap_finding(monkeypatch, tmp_path):
    """Text layer present but zero objects extracted -> failed-scope gap
    finding survives into the report."""
    sink = []
    _patch_pipeline(monkeypatch, _finding(["S401"], PAGE_TEXT), sink)
    # Extractor returns a sheet with NO objects despite a real text layer.
    monkeypatch.setattr(runner_mod, "SheetExtractorAgent", _stub_agent([
        {"sheet_number": "S401", "page_number": 1, "level": "roof",
         "discipline": "S", "assertions": []},
    ]))
    pdf = _make_pdf(tmp_path / "set.pdf")
    report = run_agent_pipeline(pdf, out_dir=str(tmp_path),
                                require_review=False)
    gaps = [f for f in (report.get("validated_issues") or [])
            if f.get("category") == "analysis_gap"]
    assert any("extraction gap" in (g.get("description") or "")
               for g in gaps)
