"""Tests for v3.9.4 temporal verification spec lint + schema conformance.

Per docs/design/2026-05-18-ars-v3.9.4-temporal-verification-spec.md §7.
"""
from __future__ import annotations

import json
from pathlib import Path

import jsonschema
import pytest
import yaml

REPO_ROOT = Path(__file__).resolve().parents[1]
SCHEMAS = REPO_ROOT / "shared/contracts/passport"


def _load_schema(name: str) -> dict:
    return json.loads((SCHEMAS / name).read_text())


def test_timeline_schema_validates_canonical_example():
    schema = _load_schema("timeline.schema.json")
    example = {
        "schema_version": "1.0",
        "sources": [
            {
                "citation_key": "handbook-2024ed",
                "type": "institutional-document",
                "published_date": {
                    "value": "2024-09-15",
                    "precision": "day",
                    "open_ended": False,
                    "provenance": {
                        "method": "crossref_lookup",
                        "raw": "2024-09-15",
                        "source_locator": "doi:10.xxxx/handbook-2024",
                        "confidence": "high",
                    },
                },
                "effective_date_range": {
                    "start": {
                        "value": "2024-10-01",
                        "precision": "day",
                        "open_ended": False,
                        "provenance": {
                            "method": "pdftotext_cover",
                            "raw": "Effective from October 1, 2024",
                            "source_locator": "file:///path/handbook-2024.pdf:p3",
                            "confidence": "high",
                        },
                    },
                    "end": {
                        "value": None,
                        "precision": "unknown",
                        "open_ended": True,
                        "provenance": {
                            "method": "user_override",
                            "confidence": "high",
                        },
                    },
                },
                "supersedes": "handbook-2020ed",
                "superseded_by": None,
                "version_family_id": "handbook-family",
                "version_catalog_completeness": "partial",
            }
        ],
        "events": [
            {
                "event_id": "programme-X-cycle-2022",
                "description": "Programme X review cycle 2022",
                "date": {
                    "value": "2022-04-01..2022-12-31",
                    "precision": "interval",
                    "open_ended": False,
                    "provenance": {
                        "method": "user_override",
                        "confidence": "high",
                    },
                },
                "governed_by": "handbook-2020ed",
            }
        ],
    }
    jsonschema.validate(example, schema)


def test_timeline_open_ended_only_on_end():
    """open_ended:true on start date should be a schema violation per spec §3.1 date shape table."""
    schema = _load_schema("timeline.schema.json")
    bad = {
        "schema_version": "1.0",
        "sources": [
            {
                "citation_key": "x",
                "type": "doc",
                "effective_date_range": {
                    "start": {
                        "value": None,
                        "precision": "unknown",
                        "open_ended": True,
                        "provenance": {"method": "unknown", "confidence": "unverified"},
                    },
                    "end": {
                        "value": "2024-12-31",
                        "precision": "day",
                        "open_ended": False,
                        "provenance": {"method": "crossref_lookup", "confidence": "high"},
                    },
                },
            }
        ],
    }
    with pytest.raises(jsonschema.ValidationError):
        jsonschema.validate(bad, schema)


def test_citation_provenance_schema_validates_canonical_example():
    schema = _load_schema("citation_provenance.schema.json")
    example = {
        "schema_version": "1.0",
        "audit_run_id": "2026-05-18T12:34:56Z-a1b2",
        "entries": [
            {
                "citation_key": "handbook-2024ed",
                "crossref_issued": {
                    "value": "2024-09-15",
                    "precision": "day",
                    "verified_at": "2026-05-18T12:34:56Z",
                    "api_endpoint": "https://api.crossref.org/works/10.xxxx/handbook-2024",
                },
                "pdftotext_cover_first_line": {
                    "line": "Quality Assurance Handbook, 2024 Edition",
                    "published_date_candidate": {
                        "value": "2024",
                        "precision": "year",
                    },
                    "verified_at": "2026-05-18T12:34:56Z",
                    "pdf_path": "/path/handbook-2024.pdf",
                },
                "verification_method": "crossref_and_pdftotext",
                "confidence": "high",
                "notes": None,
            }
        ],
    }
    jsonschema.validate(example, schema)


def test_citation_provenance_high_requires_both_sources():
    """confidence:high MUST have both crossref_issued and pdftotext_cover_first_line populated (per spec §3.4 agreement table row 1)."""
    schema = _load_schema("citation_provenance.schema.json")
    bad = {
        "schema_version": "1.0",
        "audit_run_id": "2026-05-18T12:34:56Z-a1b2",
        "entries": [
            {
                "citation_key": "x",
                "crossref_issued": None,
                "pdftotext_cover_first_line": None,
                "verification_method": "crossref_and_pdftotext",
                "confidence": "high",
                "notes": None,
            }
        ],
    }
    with pytest.raises(jsonschema.ValidationError):
        jsonschema.validate(bad, schema)


def test_citation_provenance_high_rejects_absent_sources():
    """v3.9.4.1 hotfix: confidence:high MUST also reject entries that OMIT crossref_issued
    and pdftotext_cover_first_line entirely. The v3.9.4 allOf used `then.properties` only,
    which does not fire when the property is absent — so an entry with confidence:high and
    no source fields silently passed validation. v3.9.4.1 adds `then.required` to enforce presence."""
    schema = _load_schema("citation_provenance.schema.json")
    bad = {
        "schema_version": "1.0",
        "audit_run_id": "2026-05-18T12:34:56Z-a1b2",
        "entries": [
            {
                "citation_key": "x",
                # Both crossref_issued AND pdftotext_cover_first_line absent.
                "verification_method": "crossref_and_pdftotext",
                "confidence": "high",
                "notes": None,
            }
        ],
    }
    with pytest.raises(jsonschema.ValidationError):
        jsonschema.validate(bad, schema)


@pytest.mark.parametrize("finding_kind,mode,severity,bound_refs,bound_event,bound_dates,matched_span", [
    ("TEMPORAL-ARITHMETIC-IMPOSSIBLE", 1, "HIGH", [], None,
     {"left": {"role": "anchor", "value": "2025-03", "source": "draft_capture", "ref_slug": None},
      "right": {"role": "event", "value": "2025-06", "source": "draft_capture", "ref_slug": None}},
     None),
    ("TEMPORAL-ANACHRONISTIC-CITATION", 2, "HIGH",
     [{"ref_slug": "h2026", "timeline_entry": "h2026"}],
     {"event_id": "e2022", "date": "2022-04-01..2022-12-31"}, None, None),
    ("TEMPORAL-COMPARATOR-UNMATERIALIZED", 3, "MEDIUM",
     [{"ref_slug": "s2020", "timeline_entry": "s2020"}], None, None,
     {"text": "1998 edition", "char_start": 100, "char_end": 112}),
    ("TEMPORAL-CAUSAL-INVERSION", 4, "MEDIUM",
     [{"ref_slug": "a", "timeline_entry": "a"}, {"ref_slug": "b", "timeline_entry": "b"}],
     None,
     {"left": {"role": "left_arg", "value": "2026-03-01", "source": "timeline_ref", "ref_slug": "a"},
      "right": {"role": "right_arg", "value": "2020-05-15", "source": "timeline_ref", "ref_slug": "b"}},
     {"text": "A enabled B", "char_start": 0, "char_end": 11}),
    ("TEMPORAL-DEICTIC", 5, "LOW", [], None, None,
     {"text": "currently", "char_start": 0, "char_end": 9}),
    ("TEMPORAL-METADATA-MISSING", None, "LOW", [], None, None, None),
])
def test_temporal_audit_schema_accepts_6_finding_kinds(finding_kind, mode, severity, bound_refs, bound_event, bound_dates, matched_span):
    schema = _load_schema("temporal_audit_results.schema.json")
    example = {
        "schema_version": "1.0",
        "audit_run_id": "2026-05-18T12:34:56Z-a1b2",
        "report_reference_date": "2026-05-18",
        "findings": [
            {
                "finding_id": "TF-001",
                "finding_kind": finding_kind,
                "severity": severity,
                "mode": mode,
                "block_eligible": False,
                "draft_locator": {"file": "phase4_composition/draft.md", "line": 1, "sentence": "x"},
                "matched_span": matched_span,
                "bound_refs": bound_refs,
                "bound_event": bound_event,
                "bound_dates": bound_dates,
                "rationale": "r",
                "suggested_fix": None,
            }
        ],
    }
    jsonschema.validate(example, schema)


def test_temporal_audit_p2_requires_bound_event():
    """P2 anachronism MUST have bound_event non-null per spec §3.2 per-kind map."""
    schema = _load_schema("temporal_audit_results.schema.json")
    bad = {
        "schema_version": "1.0",
        "audit_run_id": "2026-05-18T12:34:56Z-a1b2",
        "report_reference_date": "2026-05-18",
        "findings": [
            {
                "finding_id": "TF-001",
                "finding_kind": "TEMPORAL-ANACHRONISTIC-CITATION",
                "severity": "HIGH",
                "mode": 2,
                "block_eligible": True,
                "draft_locator": {"file": "x", "line": 1, "sentence": "x"},
                "matched_span": None,
                "bound_refs": [{"ref_slug": "x", "timeline_entry": "x"}],
                "bound_event": None,  # invalid for P2
                "bound_dates": None,
                "rationale": "r",
                "suggested_fix": None,
            }
        ],
    }
    with pytest.raises(jsonschema.ValidationError):
        jsonschema.validate(bad, schema)


def test_temporal_audit_p1_requires_bound_dates():
    """P1 arithmetic MUST have bound_dates non-null."""
    schema = _load_schema("temporal_audit_results.schema.json")
    bad = {
        "schema_version": "1.0",
        "audit_run_id": "2026-05-18T12:34:56Z-a1b2",
        "report_reference_date": "2026-05-18",
        "findings": [
            {
                "finding_id": "TF-001",
                "finding_kind": "TEMPORAL-ARITHMETIC-IMPOSSIBLE",
                "severity": "HIGH",
                "mode": 1,
                "block_eligible": True,
                "draft_locator": {"file": "x", "line": 1, "sentence": "x"},
                "matched_span": None,
                "bound_refs": [],
                "bound_event": None,
                "bound_dates": None,  # invalid for P1
                "rationale": "r",
                "suggested_fix": None,
            }
        ],
    }
    with pytest.raises(jsonschema.ValidationError):
        jsonschema.validate(bad, schema)


import subprocess
import sys as _sys

SCRIPT = REPO_ROOT / "scripts/check_v3_9_4_temporal_verification.py"


def test_lint_exits_zero_on_clean_fixture(tmp_path):
    """Lint should exit 0 when validating valid sample fixtures."""
    timeline = tmp_path / "timeline.yaml"
    timeline.write_text(yaml.safe_dump({
        "schema_version": "1.0",
        "sources": [],
        "events": [],
    }))
    provenance = tmp_path / "citation_provenance.yaml"
    provenance.write_text(yaml.safe_dump({
        "schema_version": "1.0",
        "audit_run_id": "2026-05-18T12:34:56Z-a1b2",
        "entries": [],
    }))
    audit = tmp_path / "temporal_audit_results.yaml"
    audit.write_text(yaml.safe_dump({
        "schema_version": "1.0",
        "audit_run_id": "2026-05-18T12:34:56Z-a1b2",
        "report_reference_date": "2026-05-18",
        "findings": [],
    }))

    result = subprocess.run(
        [_sys.executable, str(SCRIPT),
         "--timeline", str(timeline),
         "--citation-provenance", str(provenance),
         "--temporal-audit", str(audit)],
        capture_output=True, text=True,
    )
    assert result.returncode == 0, f"stdout={result.stdout!r} stderr={result.stderr!r}"


def test_lint_detects_supersession_cycle(tmp_path):
    """Invariant 2 — supersession chain must have no cycles."""
    timeline = tmp_path / "timeline.yaml"
    timeline.write_text(yaml.safe_dump({
        "schema_version": "1.0",
        "sources": [
            {"citation_key": "a", "type": "doc", "supersedes": "b", "superseded_by": None},
            {"citation_key": "b", "type": "doc", "supersedes": "a", "superseded_by": None},
        ],
        "events": [],
    }))
    provenance = tmp_path / "citation_provenance.yaml"
    provenance.write_text(yaml.safe_dump({
        "schema_version": "1.0",
        "audit_run_id": "2026-05-18T12:34:56Z-a1b2",
        "entries": [],
    }))
    audit = tmp_path / "temporal_audit_results.yaml"
    audit.write_text(yaml.safe_dump({
        "schema_version": "1.0",
        "audit_run_id": "2026-05-18T12:34:56Z-a1b2",
        "report_reference_date": "2026-05-18",
        "findings": [],
    }))

    result = subprocess.run(
        [_sys.executable, str(SCRIPT),
         "--timeline", str(timeline),
         "--citation-provenance", str(provenance),
         "--temporal-audit", str(audit)],
        capture_output=True, text=True,
    )
    assert result.returncode == 1, f"expected exit 1 for cycle, got {result.returncode}; stderr={result.stderr!r}"
    assert "cycle" in result.stderr.lower(), f"expected 'cycle' in stderr, got: {result.stderr!r}"


def test_lint_bibliography_agent_unchanged(tmp_path):
    """F2 invariant: bibliography_agent.md unmodified passes the lint."""
    timeline = tmp_path / "timeline.yaml"
    timeline.write_text(yaml.safe_dump({"schema_version": "1.0", "sources": [], "events": []}))
    provenance = tmp_path / "citation_provenance.yaml"
    provenance.write_text(yaml.safe_dump({
        "schema_version": "1.0", "audit_run_id": "2026-05-18T12:34:56Z-a1b2", "entries": []
    }))
    audit = tmp_path / "temporal_audit_results.yaml"
    audit.write_text(yaml.safe_dump({
        "schema_version": "1.0", "audit_run_id": "2026-05-18T12:34:56Z-a1b2",
        "report_reference_date": "2026-05-18", "findings": []
    }))

    result = subprocess.run(
        [_sys.executable, str(SCRIPT),
         "--timeline", str(timeline),
         "--citation-provenance", str(provenance),
         "--temporal-audit", str(audit)],
        capture_output=True, text=True,
    )
    assert result.returncode == 0, f"baseline mismatch — bibliography_agent.md was modified? stderr={result.stderr}"


def test_lint_bibliography_agent_modified_fails(tmp_path, monkeypatch):
    """F2 invariant: modified bibliography_agent.md fails the lint.

    Uses monkeypatch to point BIBLIOGRAPHY_AGENT_SHA256 at a wrong value, then runs the
    lint as a function (not subprocess) so we can override the module constant.
    """
    timeline = tmp_path / "timeline.yaml"
    timeline.write_text(yaml.safe_dump({"schema_version": "1.0", "sources": [], "events": []}))
    provenance = tmp_path / "citation_provenance.yaml"
    provenance.write_text(yaml.safe_dump({
        "schema_version": "1.0", "audit_run_id": "2026-05-18T12:34:56Z-a1b2", "entries": []
    }))
    audit = tmp_path / "temporal_audit_results.yaml"
    audit.write_text(yaml.safe_dump({
        "schema_version": "1.0", "audit_run_id": "2026-05-18T12:34:56Z-a1b2",
        "report_reference_date": "2026-05-18", "findings": []
    }))

    # Import the lint module and monkeypatch the expected sha256 to a bogus value
    import importlib.util
    spec_loader = importlib.util.spec_from_file_location("v3_9_4_lint", SCRIPT)
    lint_mod = importlib.util.module_from_spec(spec_loader)
    spec_loader.loader.exec_module(lint_mod)
    monkeypatch.setattr(lint_mod, "BIBLIOGRAPHY_AGENT_SHA256", "0" * 64)

    exit_code = lint_mod.main([
        "--timeline", str(timeline),
        "--citation-provenance", str(provenance),
        "--temporal-audit", str(audit),
    ])
    assert exit_code == 1, "expected exit 1 when sha256 baseline mismatches"


def test_timeline_extraction_agent_has_phase_boundary_block():
    agent_path = REPO_ROOT / "deep-research/agents/timeline_extraction_agent.md"
    assert agent_path.exists(), "timeline_extraction_agent.md not created"
    content = agent_path.read_text()
    # 4 load-bearing keywords from canonical v3.9.4 boundary block
    assert "## Phase Boundary (v3.9.4)" in content
    assert "MUST NOT" in content
    assert "MAY READ" in content
    assert "Enforcement (v3.9.4)" in content
    # M6 Citation Provenance Protocol section
    assert "## Citation Provenance Protocol (v3.9.4)" in content


def test_timeline_extraction_agent_lists_two_deliverables():
    agent_path = REPO_ROOT / "deep-research/agents/timeline_extraction_agent.md"
    content = agent_path.read_text()
    assert "timeline.yaml" in content
    assert "citation_provenance.yaml" in content


M3_IRON_RULE_MARKER = "## Temporal Integrity Iron Rule (v3.9.4)"
M3_KEY_PHRASES = [
    "Temporal claims are arithmetic, not stylistic.",
    "Identify the date or date range of every entity",
    "verify the cited document existed BEFORE the event",
]


def test_m3_iron_rule_present_in_report_compiler():
    path = REPO_ROOT / "deep-research/agents/report_compiler_agent.md"
    content = path.read_text()
    assert M3_IRON_RULE_MARKER in content
    for phrase in M3_KEY_PHRASES:
        assert phrase in content, f"missing phrase: {phrase}"


def test_m3_iron_rule_present_in_draft_writer():
    path = REPO_ROOT / "academic-paper/agents/draft_writer_agent.md"
    content = path.read_text()
    assert M3_IRON_RULE_MARKER in content
    for phrase in M3_KEY_PHRASES:
        assert phrase in content
