"""MCP tools for the External Precedent Library.

This is distinct from:

- ``precedents`` (case_precedents table) — chair-attached quotes scoped to
  a specific case section. Use ``search_case_precedents`` for that (GAP-49:
  renamed from the misleading ``precedent_search_library``).
- ``style_corpus`` (Daphna's prior decisions) — searched via
  ``search_decisions`` for style/voice.

The precedent library is the **authoritative law** corpus: external court
rulings and other appeals committees' decisions, with halachot extracted
and reviewed by the chair.

All halachot enter as ``pending_review`` and are invisible to search until
the chair approves them — per project review policy.
"""

from __future__ import annotations

import time
from uuid import UUID

from legal_mcp.services import canonical_synthesis, db, precedent_library, telemetry
from legal_mcp.tools.envelope import empty, err as _err, ok as _ok  # GAP-48: SSoT envelope


async def precedent_library_upload(
    file_path: str,
    citation: str,
    case_name: str = "",
    court: str = "",
    decision_date: str = "",
    source_type: str = "",
    precedent_level: str = "",
    practice_area: str = "",
    appeal_subtype: str = "",
    subject_tags: list[str] | None = None,
    is_binding: bool = True,
    headnote: str = "",
    summary: str = "",
) -> str:
    """העלאת פסיקה חיצונית לקורפוס הסמכותי + חילוץ הלכות אוטומטי.

    Args:
        file_path: נתיב מלא לקובץ PDF/DOCX/RTF/TXT/MD.
        citation: מראה המקום ("עע\\"מ 3975/22 ב. קרן-נכסים נ' ועדה מקומית").
        case_name: שם קצר.
        court: ערכאה (עליון / מנהלי / ועדת ערר ארצית / ועדת ערר מחוזית).
        decision_date: ISO date (YYYY-MM-DD), אופציונלי.
        source_type: court_ruling / appeals_committee.
        precedent_level: עליון / מנהלי / ועדת_ערר_ארצית / ועדת_ערר_מחוזית.
        practice_area: rishuy_uvniya / betterment_levy / compensation_197.
        subject_tags: תגיות נושא (חניה, קווי_בניין, וכד').

    Returns: JSON עם case_law_id, מספר chunks, מספר הלכות שנכנסו לתור אישור.
    """
    if not citation.strip():
        return _err("citation חובה")
    # Citation guard: appeals-committee decisions must go through
    # internal_decision_upload (with chair_name + district). The legacy
    # path always stored source_kind='external_upload' and left
    # chair_name/district empty — see TaskMaster #30(ב).
    _norm = citation.strip()
    _committee_prefixes = ("ערר ", "ערר(", "ערר ", "בל\"מ ", "בל\"מ(", "ARAR ")
    if any(_norm.startswith(p) for p in _committee_prefixes):
        return _err(
            "ציטוט שמתחיל ב-'ערר' או 'בל\"מ' הוא החלטת ועדת ערר. "
            "השתמש ב-internal_decision_upload (דורש chair_name + district), "
            "לא ב-precedent_library_upload."
        )
    try:
        result = await precedent_library.ingest_precedent(
            file_path=file_path,
            citation=citation,
            case_name=case_name,
            court=court,
            decision_date=decision_date or None,
            source_type=source_type,
            precedent_level=precedent_level,
            practice_area=practice_area,
            appeal_subtype=appeal_subtype,
            subject_tags=subject_tags or [],
            is_binding=is_binding,
            headnote=headnote,
            summary=summary,
        )
    except Exception as e:
        return _err(str(e))
    return _ok(result)


async def precedent_library_list(
    practice_area: str = "",
    court: str = "",
    precedent_level: str = "",
    source_type: str = "",
    search: str = "",
    source_kind: str = "",
    limit: int = 100,
) -> str:
    """רשימה של פסיקה בקורפוס הסמכותי, עם פילטרים.

    source_kind ריק (ברירת מחדל) = כל הקורפוס, כולל החלטות ועדות ערר.
    """
    rows = await precedent_library.list_precedents(
        practice_area=practice_area,
        court=court,
        precedent_level=precedent_level,
        source_type=source_type,
        search=search,
        source_kind=source_kind,
        limit=limit,
    )
    return _ok(rows)


async def precedent_library_get(case_law_id: str) -> str:
    """פסיקה ספציפית עם כל ההלכות שלה (כולל ממתינות לאישור)."""
    try:
        cid = UUID(case_law_id)
    except ValueError:
        return _err("case_law_id לא תקין")
    record = await precedent_library.get_precedent(cid)
    if not record:
        return _err("פסיקה לא נמצאה")
    return _ok(record)


async def precedent_link_cases(
    case_law_id_a: str,
    case_law_id_b: str,
    relation_type: str = "same_case_chain",
) -> str:
    """קישור שתי פסיקות כקשורות זו לזו (דו-כיווני). idempotent.

    Args:
        case_law_id_a: UUID של פסיקה ראשונה.
        case_law_id_b: UUID של פסיקה שנייה.
        relation_type: same_case_chain | overruled_by | distinguished
    """
    try:
        a = UUID(case_law_id_a)
        b = UUID(case_law_id_b)
    except ValueError:
        return _err("case_law_id לא תקין")
    rec_a = await db.get_case_law(a)
    rec_b = await db.get_case_law(b)
    if not rec_a:
        return _err(f"פסיקה {case_law_id_a} לא נמצאה")
    if not rec_b:
        return _err(f"פסיקה {case_law_id_b} לא נמצאה")
    await db.add_case_law_relation(a, b, relation_type)
    return _ok({
        "linked": True,
        "relation_type": relation_type,
        "a": {"id": case_law_id_a, "case_number": rec_a.get("case_number"), "court": rec_a.get("court")},
        "b": {"id": case_law_id_b, "case_number": rec_b.get("case_number"), "court": rec_b.get("court")},
    })


async def precedent_unlink_cases(case_law_id_a: str, case_law_id_b: str) -> str:
    """הסרת קישור בין שתי פסיקות (דו-כיווני).

    Args:
        case_law_id_a: UUID של פסיקה ראשונה.
        case_law_id_b: UUID של פסיקה שנייה.
    """
    try:
        a = UUID(case_law_id_a)
        b = UUID(case_law_id_b)
    except ValueError:
        return _err("case_law_id לא תקין")
    await db.remove_case_law_relation(a, b)
    return _ok({"unlinked": True, "a": case_law_id_a, "b": case_law_id_b})


async def precedent_library_delete(case_law_id: str) -> str:
    """מחיקת פסיקה מהקורפוס. cascade: chunks + halachot."""
    try:
        cid = UUID(case_law_id)
    except ValueError:
        return _err("case_law_id לא תקין")
    ok = await precedent_library.delete_precedent(cid)
    return _ok({"deleted": ok, "case_law_id": case_law_id})


async def precedent_extract_halachot(case_law_id: str) -> str:
    """הרצה מחדש של חילוץ ההלכות לפסיקה קיימת. הלכות שאושרו/פורסמו נשמרות (INV-G10); רק הלכות שלא-נבדקו מוחלפות."""
    try:
        cid = UUID(case_law_id)
    except ValueError:
        return _err("case_law_id לא תקין")
    try:
        result = await precedent_library.reextract_halachot(cid)
    except Exception as e:
        return _err(str(e))
    return _ok(result)


async def precedent_extract_metadata(case_law_id: str) -> str:
    """חילוץ מטא-דאטה (case_name קצר, summary, headnote, key_quote, subject_tags, appeal_subtype, date, level, court, source_type) מהטקסט. ממלא רק שדות ריקים — לא דורס מה שכבר הוזן."""
    try:
        cid = UUID(case_law_id)
    except ValueError:
        return _err("case_law_id לא תקין")
    try:
        result = await precedent_library.reextract_metadata(cid)
    except Exception as e:
        return _err(str(e))
    return _ok(result)


async def precedent_reindex(case_law_id: str) -> str:
    """re-chunk + re-embed פסיקה קיימת מה-full_text השמור (FU-3/GAP-09).

    לתיקון drift של embeddings או אחרי שינוי-תוכן. אינו מריץ OCR/LLM — רק
    chunking + voyage embeddings. idempotent (מוחק ובונה chunks מחדש).
    """
    try:
        cid = UUID(case_law_id)
    except ValueError:
        return _err("case_law_id לא תקין")
    try:
        from legal_mcp.services import ingest
        result = await ingest.reindex_case_law(cid)
    except Exception as e:
        return _err(str(e))
    return _ok(result)


async def extraction_status() -> str:
    """סטטוס תור-החילוץ — כמה פסיקות ממתינות לחילוץ metadata/halacha (INV-TOOL4 / GAP-45).

    חושף את התור ש-precedent_process_pending מרוקן: עומק-תור + גיל הבקשה
    הוותיקה ביותר לכל סוג. read-only — אינו מרוקן את התור.
    """
    try:
        status = await db.extraction_queue_status()
    except Exception as e:
        return _err(str(e))
    return _ok(status)


async def precedent_process_pending(kind: str = "metadata", limit: int = 20) -> str:
    """ריקון תור בקשות חילוץ שנערמו ע"י כפתורי ה-UI. kind: 'metadata' או 'halacha'.

    הכפתור ב-UI מסמן ב-DB שהפסיקה מבקשת חילוץ. כלי זה (שרץ מקומית עם CLI)
    סורק את התור ומריץ את ה-extractor לכל פריט. אחרי הצלחה הסימון מתנקה.
    """
    if kind not in {"metadata", "halacha"}:
        return _err("kind חייב להיות 'metadata' או 'halacha'")
    try:
        result = await precedent_library.process_pending_extractions(
            kind=kind, limit=limit,
        )
    except Exception as e:
        return _err(str(e))
    return _ok(result)


async def search_precedent_library(
    query: str,
    practice_area: str = "",
    court: str = "",
    precedent_level: str = "",
    appeal_subtype: str = "",
    is_binding: bool | None = None,
    subject_tag: str = "",
    limit: int = 10,
    include_halachot: bool = True,
    source_kind: str = "",
) -> str:
    """חיפוש סמנטי בקורפוס הפסיקה הסמכותית — פסקי דין **והחלטות ועדות ערר**.

    מחזיר תוצאות מעורבות: הלכות (rule-level, מאושרות בלבד) + קטעי טקסט
    (passage-level). הלכות מקבלות boost קל בדירוג כי הן מזוקקות מראש.

    Args:
        query: שאילתת חיפוש בעברית.
        practice_area: rishuy_uvniya / betterment_levy / compensation_197.
        court: סינון לפי ערכאה (substring).
        precedent_level: עליון / מנהלי / ועדת_ערר_ארצית / ועדת_ערר_מחוזית.
        appeal_subtype: סינון לתת-סוג.
        is_binding: True/False (None = ללא סינון).
        subject_tag: סינון לפי תגית נושא (לדוגמה "מועד_קביעת_שומה").
        limit: מספר תוצאות מקסימלי.
        include_halachot: האם לכלול הלכות (ברירת מחדל: כן).
        source_kind: ריק (ברירת מחדל) = כל הקורפוס — פסקי דין והחלטות ועדות
            ערר יחד. "external_upload" = פסקי בתי משפט בלבד;
            "internal_committee" = החלטות ועדות ערר בלבד.

    Returns: רשימה מדורגת. כל פריט הוא {"type": "halacha"|"passage", "score", ...}.
    """
    if not query or len(query.strip()) < 2:
        return empty("שאילתה קצרה מדי (פחות מ-2 תווים).")
    q = query.strip()
    t0 = time.perf_counter()
    results = await precedent_library.search_library(
        query=q,
        practice_area=practice_area,
        court=court,
        precedent_level=precedent_level,
        appeal_subtype=appeal_subtype,
        is_binding=is_binding,
        subject_tag=subject_tag,
        limit=limit,
        include_halachot=include_halachot,
        source_kind=source_kind,
    )
    # X11 Phase 2 (#154): attach the incoming-citation authority breakdown so the
    # research agent can WEIGH and ARGUE authority ("הלכה שאומצה ב-N החלטות ועדת-ערר")
    # — and steer clear of a precedent that committees repeatedly distinguished /
    # overruled. Batched: one query for the whole result page.
    try:
        ids = {str(r.get("case_law_id")) for r in results if r.get("case_law_id")}
        if ids:
            auth = await db.citation_authority([UUID(i) for i in ids])
            for r in results:
                cb = auth.get(str(r.get("case_law_id")))
                if cb:
                    r["cited_by"] = cb
    except Exception:  # noqa: BLE001 — authority is an additive signal; never break search
        pass
    elapsed_ms = int((time.perf_counter() - t0) * 1000)
    telemetry.log_search_bg(
        search_type="precedent_library",
        query=q,
        results=results,
        duration_ms=elapsed_ms,
        practice_area=practice_area or None,
        user_agent="unknown",
    )
    return _ok(results)


async def halacha_review(
    halacha_id: str,
    status: str,
    reviewer: str = "דפנה",
    rule_statement: str = "",
    reasoning_summary: str = "",
    subject_tags: list[str] | None = None,
    practice_areas: list[str] | None = None,
    canonical_statement: str = "",
) -> str:
    """אישור / דחייה / עריכה של הלכה שחולצה אוטומטית.

    Args:
        halacha_id: מזהה ההלכה.
        status: pending_review / approved / rejected / published.
        reviewer: שם המאשר (ברירת מחדל: דפנה).
        rule_statement: עריכת ניסוח הכלל (ריק = ללא שינוי).
        reasoning_summary: עריכת תמצית ההיגיון (ריק = ללא שינוי).
        subject_tags: עריכת תגיות (None = ללא שינוי).
        practice_areas: עריכת תחומים (None = ללא שינוי).
        canonical_statement: עריכת הניסוח הקנוני הרחב של העיקרון (ריק = ללא שינוי).
    """
    if status not in {"pending_review", "approved", "rejected", "published"}:
        return _err(
            "status לא חוקי. ערכים תקינים: "
            "pending_review / approved / rejected / published"
        )
    try:
        hid = UUID(halacha_id)
    except ValueError:
        return _err("halacha_id לא תקין")

    row = await db.update_halacha(
        halacha_id=hid,
        review_status=status,
        reviewer=reviewer,
        rule_statement=rule_statement or None,
        reasoning_summary=reasoning_summary or None,
        subject_tags=subject_tags,
        practice_areas=practice_areas,
    )
    if row is None:
        return _err("הלכה לא נמצאה")

    # V41: propagate canonical_statement edit to the canonical principle.
    if canonical_statement and row.get("canonical_id"):
        try:
            await db.update_canonical_statement(
                UUID(str(row["canonical_id"])), canonical_statement,
            )
        except Exception as e:
            import logging
            logging.getLogger(__name__).warning(
                "halacha_review: failed to update canonical_statement: %s", e,
            )

    return _ok(row)


async def halachot_pending(
    limit: int = 100,
    include_low_quality: bool = False,
    instance_type: str = "original",
) -> str:
    """תור ההלכות הממתינות לאישור (review_status='pending_review').

    כברירת-מחדל (#84.1, #84.3, V41) התור **מסונן** — הלכות עם דגל-איכות כלשהו
    (application / ציטוט-לא-מאומת / קטוע / obiter / restatement דק / לא-נתמך /
    near-duplicate) מוסתרות (הן שייכות ל'דורש תיקון-חילוץ', לא לתור-האישור),
    ו**ממוין לפי עדיפות** (טופלו-לרעה תחילה, אז הכי לא-ודאיים, אז הישנים).

    V41: כברירת-מחדל מציג רק instance_type='original' (עקרונות חדשים, לא ציטוטים).
    העברת instance_type='' מציגה הכל (כולל ציטוטים).

    Args:
        limit: מספר מקסימלי.
        include_low_quality: True כדי לחשוף גם פריטים מסומני-איכות (בקט 'דורש תיקון').
        instance_type: 'original' (ברירת מחדל) / 'citation' / 'application' / '' (הכל).
    """
    rows = await db.list_halachot(
        review_status="pending_review",
        instance_type=instance_type or None,
        limit=limit,
        exclude_low_quality=not include_low_quality,
        order_by_priority=True,
    )
    return _ok(rows)


async def canonical_halacha_list(
    practice_area: str = "",
    review_status: str = "",
    limit: int = 50,
    offset: int = 0,
) -> str:
    """רשימת עקרונות משפטיים קנוניים — שאילתת נוחות לסוכני-הכתיבה.

    כל פריט כולל principle_label לפי מקורו (#152): 'הלכה' (פס"ד מחוזי/עליון מחייב),
    'כלל פרשני' (החלטת ועדת-ערר), או 'עיקרון' (פסיקה משכנעת).

    Args:
        practice_area: סינון לפי תחום עיסוק (ריק = הכל).
        review_status: pending_synthesis / pending_review / approved / published (ריק = הכל).
        limit: מספר מקסימלי (עד 200).
        offset: עמוד (pagination).
    """
    rows = await db.list_canonical_halachot(
        practice_area=practice_area or None,
        review_status=review_status or None,
        limit=min(limit, 200),
        offset=offset,
    )
    return _ok(rows)


async def canonical_halacha_get(canonical_id: str) -> str:
    """שלוף עיקרון קנוני אחד (canonical_statement, practice_areas, subject_tags,
    review_status, instance_count) + כל האינסטנסים שלו (לפי פסיקה).

    Args:
        canonical_id: מזהה (UUID) של העיקרון הקנוני.
    """
    try:
        cid = UUID(canonical_id)
    except ValueError:
        return _err("canonical_id לא תקין")
    row = await db.get_canonical_halacha(cid)
    if row is None:
        return _err("עיקרון קנוני לא נמצא")
    return _ok(row)


async def canonical_synthesize_pending(limit: int = 20) -> str:
    """סנתז ניסוח-קנוני לעקרונות הממתינים (review_status='pending_synthesis'). V41 Phase 4.

    לכל עיקרון: מודל מקומי (claude_session) מזקק ניסוח אחד, כללי ומעוגן בציטוטי-המופעים
    (INV-AH), שער-drift דוחה סטייה גדולה מדי, והסטטוס מתקדם ל-pending_review לשער-היו"ר
    (G10). on-demand / burst ידני; המסה הראשונית מטופלת ב-backfill_canonical_synthesis.py.

    Args:
        limit: מספר מקסימלי לסבב (עד 100). רב-instance מטופלים ראשונים.
    """
    pool = await db.get_pool()
    rows = await pool.fetch(
        "SELECT id::text AS id FROM canonical_halachot "
        "WHERE review_status='pending_synthesis' "
        "ORDER BY instance_count DESC, created_at LIMIT $1",
        min(max(limit, 1), 100),
    )
    if not rows:
        return _ok({"processed": 0, "results": [], "message": "אין עקרונות ממתינים לסינתזה"})
    results = []
    counts: dict[str, int] = {}
    for r in rows:
        res = await canonical_synthesis.synthesize_and_apply(UUID(r["id"]))
        counts[res["status"]] = counts.get(res["status"], 0) + 1
        results.append({
            "canonical_id": res["canonical_id"], "status": res["status"],
            "drift_cosine": res.get("drift_cosine"), "reason": res.get("reason", ""),
        })
    return _ok({"processed": len(results), "by_status": counts, "results": results})
