"""Tests for the comparative protocol analyzer (WS4 / #203).

Covers the pure (non-LLM, non-DB) logic: the anti-hallucination normalization
gate, protocol-document discovery, claim_type tagging for protocols, and the
block-ז filter that keeps oral hearing arguments out of the original-pleadings
summary (INV-WR4).
"""
from __future__ import annotations

import pytest

from legal_mcp.services import protocol_analyzer as pa
from legal_mcp.services.claims_extractor import _infer_claim_type


# ── claim_type tagging — protocol distinct from written pleadings ───────────

@pytest.mark.parametrize("doc_type,title,expected", [
    ("protocol", "פרוטוקול דיון", "protocol"),
    ("appeal", "פרוטוקול הדיון מיום 1.1", "protocol"),   # title-based
    ("appeal", "כתב ערר", "claim"),
    ("response", "כתב תשובה", "response"),
    ("response", "תגובת המשיבה", "reply"),
])
def test_infer_claim_type(doc_type, title, expected):
    assert _infer_claim_type(doc_type, title) == expected


# ── _find_protocol ─────────────────────────────────────────────────────────

def test_find_protocol_by_doc_type():
    docs = [
        {"id": "1", "doc_type": "appeal", "title": "כתב ערר"},
        {"id": "2", "doc_type": "protocol", "title": "פרוטוקול"},
    ]
    assert pa._find_protocol(docs)["id"] == "2"


def test_find_protocol_by_title_fallback():
    docs = [
        {"id": "1", "doc_type": "reference", "title": "פרוטוקול הדיון"},
        {"id": "2", "doc_type": "appeal", "title": "כתב ערר"},
    ]
    assert pa._find_protocol(docs)["id"] == "1"


def test_find_protocol_none():
    docs = [{"id": "1", "doc_type": "appeal", "title": "כתב ערר"}]
    assert pa._find_protocol(docs) is None


def test_find_protocol_skips_lower_scope():
    # A local/district-committee protocol (scope='lower') must NOT be picked for
    # the hearing-vs-pleadings comparison; the ועדת-ערר one wins (#223).
    docs = [
        {"id": "lower", "doc_type": "protocol", "title": "נספח 18 — פרוטוקולי ועדה מקומית",
         "metadata": {"protocol_scope": "lower"}, "created_at": "2026-01-01"},
        {"id": "appeal", "doc_type": "protocol", "title": "פרוטוקול דיון 23.6.26",
         "metadata": {}, "created_at": "2026-06-23"},
    ]
    assert pa._find_protocol(docs)["id"] == "appeal"


def test_find_protocol_all_lower_returns_none():
    # If every protocol is lower-scoped there is no ערר-hearing to compare.
    docs = [
        {"id": "1", "doc_type": "protocol", "title": "פרוטוקול מקומי",
         "metadata": {"protocol_scope": "lower"}},
    ]
    assert pa._find_protocol(docs) is None


def test_find_protocol_prefers_most_recent_appeal():
    docs = [
        {"id": "old", "doc_type": "protocol", "title": "פרוטוקול א",
         "metadata": {}, "created_at": "2026-03-01"},
        {"id": "new", "doc_type": "protocol", "title": "פרוטוקול ב",
         "metadata": {}, "created_at": "2026-06-23"},
    ]
    assert pa._find_protocol(docs)["id"] == "new"


def test_find_protocol_explicit_document_id_overrides():
    # Explicit target wins even over scope/recency heuristics — including the
    # ability to point at a lower-scoped doc if the caller insists.
    docs = [
        {"id": "aaaaaaaa-0000-0000-0000-000000000001", "doc_type": "protocol",
         "title": "פרוטוקול ערר", "metadata": {}, "created_at": "2026-06-23"},
        {"id": "aaaaaaaa-0000-0000-0000-000000000002", "doc_type": "protocol",
         "title": "פרוטוקול מקומי", "metadata": {"protocol_scope": "lower"},
         "created_at": "2026-01-01"},
    ]
    from uuid import UUID
    picked = pa._find_protocol(
        docs, document_id=UUID("aaaaaaaa-0000-0000-0000-000000000002"),
    )
    assert picked["id"] == "aaaaaaaa-0000-0000-0000-000000000002"


def test_find_protocol_unknown_document_id_returns_none():
    from uuid import UUID
    docs = [
        {"id": "aaaaaaaa-0000-0000-0000-000000000001", "doc_type": "protocol",
         "title": "פרוטוקול", "metadata": {}},
    ]
    assert pa._find_protocol(
        docs, document_id=UUID("bbbbbbbb-0000-0000-0000-000000000009"),
    ) is None


# ── _normalize_change — anti-hallucination gate (INV-AH) ───────────────────

_AID = "11111111-1111-1111-1111-111111111111"


def test_normalize_change_valid():
    row = pa._normalize_change(
        {
            "change_type": "dropped",
            "party_role": "appellant",
            "argument_id": _AID,
            "argument_title": "טענת השיהוי",
            "summary": "העורר ויתר על הטענה בדיון.",
            "sharpened_question": "האם נותרה טענת סף?",
            "evidence_quote": "ב\"כ העורר: איננו עומדים על טענת השיהוי.",
            "page_number": 3,
        },
        valid_argument_ids={_AID},
    )
    assert row is not None
    assert row["change_type"] == "dropped"
    assert str(row["argument_id"]) == _AID
    assert row["page_number"] == 3


def test_normalize_change_drops_row_without_evidence_quote():
    # quote-or-retract: no verbatim support → row is rejected at source.
    row = pa._normalize_change(
        {"change_type": "strengthened", "summary": "חוזקה", "evidence_quote": ""},
        valid_argument_ids=set(),
    )
    assert row is None


def test_normalize_change_drops_row_without_summary():
    row = pa._normalize_change(
        {"change_type": "dropped", "summary": "", "evidence_quote": "ציטוט"},
        valid_argument_ids=set(),
    )
    assert row is None


def test_normalize_change_rejects_bad_change_type():
    row = pa._normalize_change(
        {"change_type": "modified", "summary": "x", "evidence_quote": "y"},
        valid_argument_ids=set(),
    )
    assert row is None


def test_normalize_change_drops_hallucinated_argument_id():
    # An argument_id that isn't one of this case's arguments must be dropped
    # (FK safety) — but the row itself, being newly_raised-shaped, survives.
    row = pa._normalize_change(
        {
            "change_type": "newly_raised",
            "party_role": "respondent",
            "argument_id": "99999999-9999-9999-9999-999999999999",
            "summary": "סוגיה חדשה שעלתה בדיון.",
            "evidence_quote": "ב\"כ המשיבה העלה לראשונה את שאלת הסמכות.",
        },
        valid_argument_ids={_AID},
    )
    assert row is not None
    assert row["argument_id"] is None
    assert row["change_type"] == "newly_raised"


def test_normalize_change_invalid_party_role_blanked():
    row = pa._normalize_change(
        {
            "change_type": "strengthened",
            "party_role": "judge",      # not a valid party
            "summary": "חוזקה הטענה.",
            "evidence_quote": "ציטוט מבסס.",
        },
        valid_argument_ids=set(),
    )
    assert row is not None
    assert row["party_role"] == ""


def test_normalize_change_truncates_quote():
    long_quote = "א" * 500
    row = pa._normalize_change(
        {"change_type": "dropped", "summary": "x", "evidence_quote": long_quote},
        valid_argument_ids=set(),
    )
    assert row is not None
    assert len(row["evidence_quote"]) == 200
