"""בקרת איכות וולידציה של החלטות לפני ייצוא.

6 בדיקות:
1. neutral_background — רקע ניטרלי (ללא מילות שיפוט/ציטוטים מצדדים)
2. claims_coverage — כל טענה מבלוק ז נענתה בבלוק י
3. weight_compliance — משקלות בלוקים בטווח הנכון
4. structural_integrity — כל בלוקי חובה קיימים
5. no_duplication — אין כפילויות בין רקע לדיון
6. sequential_numbering — מספור רציף

אם בדיקה קריטית נכשלת → חוסם ייצוא.
"""

from __future__ import annotations

import json
import logging
import re
from uuid import UUID

from legal_mcp import config
from legal_mcp.config import parse_llm_json
from legal_mcp.services import db, claude_session

logger = logging.getLogger(__name__)

# ── Value/judgment words forbidden in background ──────────────────

VALUE_WORDS = [
    "חריג", "חטא", "בעייתי", "מזעזע", "שערורייתי", "מגוחך",
    "נפשע", "פגום", "חמור", "מקומם", "בלתי סביר", "מופרז",
    "מגונה", "פסול", "נלוז", "מטריד", "שגוי", "מוטעה",
]

QUOTE_INDICATORS = [
    r"לטענת\s+(העוררי|המשיב|מבקשי)",
    r"לדברי\s+(העוררי|המשיב|מבקשי)",
    r"העורר\s+טוען",
    r"המשיבה\s+טוענת",
    r"לשיטת\s+(העוררי|המשיב)",
]

# ── Weight ranges ─────────────────────────────────────────────────

WEIGHT_RANGES = {
    "licensing": {
        "block-he": (0.5, 5), "block-vav": (3, 40), "block-zayin": (13, 40),
        "block-chet": (0, 15), "block-tet": (0, 15),
        "block-yod": (30, 75), "block-yod-alef": (1, 10),
    },
    "betterment": {
        "block-he": (0, 5), "block-vav": (2, 20), "block-zayin": (15, 40),
        "block-chet": (0, 25), "block-tet": (0, 15),
        "block-yod": (25, 75), "block-yod-alef": (1, 10),
    },
    "compensation": {
        "block-he": (0, 5), "block-vav": (2, 20), "block-zayin": (15, 40),
        "block-chet": (0, 25), "block-tet": (0, 15),
        "block-yod": (25, 75), "block-yod-alef": (1, 10),
    },
}


# ── Individual checks ─────────────────────────────────────────────

def check_neutral_background(blocks: list[dict]) -> dict:
    """בדיקת ניטרליות בלוק הרקע (ו)."""
    vav = next((b for b in blocks if b["block_id"] == "block-vav"), None)
    if not vav or not vav.get("content"):
        return {"name": "neutral_background", "passed": True, "errors": [], "severity": "warning"}

    errors = []
    lines = vav["content"].split("\n")
    for i, line in enumerate(lines):
        for word in VALUE_WORDS:
            if word in line:
                errors.append(f"מילת שיפוט (שורה {i+1}): \"{word}\"")
        for pattern in QUOTE_INDICATORS:
            if re.search(pattern, line):
                errors.append(f"ציטוט מצד (שורה {i+1}): \"{line[:60]}...\"")

    return {
        "name": "neutral_background",
        "passed": len(errors) == 0,
        "errors": errors,
        "severity": "warning",
    }




CLAIMS_CHECK_PROMPT = """אתה בודק איכות החלטות משפטיות. קיבלת רשימת טענות שהועלו בכתבי הטענות, ואת בלוק הדיון של ההחלטה.

## משימה:
לכל טענה ממוספרת, קבע אם היא **נענתה** בדיון — גם אם בניסוח שונה.

## קריטריונים:
- "addressed" — הדיון מתייחס לנושא הטענה, גם אם במילים אחרות
- "partial" — הדיון נוגע בנושא אך לא עונה ישירות
- "missing" — הדיון לא מתייחס לטענה כלל

## פלט JSON בלבד:
{"results": [{"claim": 1, "status": "addressed|partial|missing", "where": "הפניה קצרה לאיפה בדיון"}]}
"""


async def check_claims_coverage(blocks: list[dict], claims: list[dict], outcome: str = "") -> dict:
    """בדיקה סמנטית (Claude) שכל טענה נענתה בדיון."""
    yod = next((b for b in blocks if b["block_id"] == "block-yod"), None)
    if not yod or not yod.get("content"):
        return {"name": "claims_coverage", "passed": False,
                "errors": ["בלוק דיון (י) ריק"], "severity": "critical"}

    if not claims:
        return {"name": "claims_coverage", "passed": True, "errors": [], "severity": "critical"}

    # #87/GAP-87 — only the appellant's claims from the APPEAL PLEADING itself
    # must be addressed. claim_type: 'claim'=כתב ערר (mandatory), 'response'=כתב
    # תשובה, 'reply'=תגובה/השלמת-טיעון/תכתובת (supplementary correspondence — NOT
    # a standalone duty to answer, especially on full acceptance). Counting reply/
    # correspondence claims as "unanswered" produced false QA fails (1033-25).
    source_claims = [
        c for c in claims
        if c.get("source_document", "") != "block-zayin"
        and c.get("claim_type") == "claim"
        and c.get("party_role") == "appellant"
    ]
    if not source_claims:
        # Fallback: appellant/respondent pleadings, excluding supplementary replies.
        source_claims = [
            c for c in claims
            if c.get("source_document", "") != "block-zayin"
            and c.get("claim_type") != "reply"
            and c.get("party_role") in ("appellant", "respondent")
        ]
    if not source_claims:
        source_claims = [c for c in claims if c.get("source_document", "") != "block-zayin"]
    if not source_claims:
        source_claims = claims

    # Build claims list
    claims_text = ""
    for i, c in enumerate(source_claims, 1):
        claims_text += f"טענה #{i}: {c['claim_text'][:300]}\n"

    # Send full discussion — don't truncate
    discussion = yod["content"]

    prompt = f"""{CLAIMS_CHECK_PROMPT}

## טענות ({len(source_claims)}):
{claims_text}

## בלוק הדיון:
{discussion}"""

    parsed = await claude_session.query_json(prompt, tools="")  # no tool_use → no error_max_turns
    if parsed is None:
        logger.warning("Failed to parse claims check")
        # Fallback: assume all covered (don't block export on parse failure)
        return {"name": "claims_coverage", "passed": True,
                "errors": ["שגיאה בפענוח תוצאות — לא ניתן לבדוק"], "severity": "warning"}

    results = parsed.get("results", [])
    missing = [r for r in results if r.get("status") == "missing"]
    partial = [r for r in results if r.get("status") == "partial"]
    addressed = [r for r in results if r.get("status") == "addressed"]

    errors = []
    for r in missing:
        idx = r.get("claim", 0)
        claim_text = source_claims[idx - 1]["claim_text"][:80] if 0 < idx <= len(source_claims) else "?"
        errors.append(f"טענה #{idx} לא נענתה: \"{claim_text}...\"")

    total = len(source_claims)
    covered = len(addressed) + len(partial)

    # On full acceptance the appellant prevailed in full — not every sub-claim
    # needs individual treatment (the chair noted this for correspondence claims,
    # 1033-25). Relax the missing-tolerance accordingly.
    allowed_missing_ratio = 0.4 if outcome == "full_acceptance" else 0.2

    return {
        "name": "claims_coverage",
        "passed": len(missing) <= total * allowed_missing_ratio,
        "errors": errors,
        "severity": "critical",
        "details": f"{covered}/{total} טענות נענו ({covered/total*100:.0f}%), {len(partial)} חלקית, {len(missing)} חסרות",
    }


def check_weight_compliance(blocks: list[dict], appeal_type: str) -> dict:
    """בדיקת משקלות בלוקים בטווח."""
    ranges = WEIGHT_RANGES.get(appeal_type, WEIGHT_RANGES["licensing"])
    total_words = sum(b.get("word_count", 0) for b in blocks)

    if total_words == 0:
        return {"name": "weight_compliance", "passed": False,
                "errors": ["אין תוכן בהחלטה"], "severity": "critical"}

    errors = []
    for block in blocks:
        bid = block["block_id"]
        wc = block.get("word_count", 0)
        if bid in ranges and wc > 0:
            weight = wc / total_words * 100
            low, high = ranges[bid]
            if weight < low:
                errors.append(f"{block.get('title', bid)}: {weight:.1f}% (מינימום: {low}%)")
            elif weight > high:
                errors.append(f"{block.get('title', bid)}: {weight:.1f}% (מקסימום: {high}%)")

    return {
        "name": "weight_compliance",
        "passed": len(errors) == 0,
        "errors": errors,
        "severity": "warning",
    }


def check_structural_integrity(blocks: list[dict]) -> dict:
    """בדיקת מבנה — כל בלוקי חובה קיימים."""
    required = {"block-he", "block-zayin", "block-yod", "block-yod-alef"}
    present = {b["block_id"] for b in blocks if b.get("word_count", 0) > 0}
    missing = required - present

    errors = []
    if missing:
        block_names = {"block-he": "פתיחה (ה)", "block-zayin": "טענות (ז)",
                       "block-yod": "דיון (י)", "block-yod-alef": "סיכום (יא)"}
        for m in missing:
            errors.append(f"בלוק חובה חסר: {block_names.get(m, m)}")

    # Check discussion is the heaviest content block
    content_blocks = [b for b in blocks if b["block_id"] not in
                      ("block-alef", "block-bet", "block-gimel", "block-dalet", "block-yod-bet")]
    if content_blocks:
        heaviest = max(content_blocks, key=lambda x: x.get("word_count", 0))
        if heaviest["block_id"] != "block-yod":
            errors.append(f"בלוק הדיון אינו הגדול ביותר — {heaviest.get('title', '')} גדול יותר")

    return {
        "name": "structural_integrity",
        "passed": len(errors) == 0,
        "errors": errors,
        "severity": "critical",
    }


def check_no_duplication(blocks: list[dict]) -> dict:
    """בדיקת כפילויות בין רקע לדיון."""
    vav = next((b for b in blocks if b["block_id"] == "block-vav"), None)
    yod = next((b for b in blocks if b["block_id"] == "block-yod"), None)

    if not vav or not yod:
        return {"name": "no_duplication", "passed": True, "errors": [], "severity": "warning"}

    vav_text = vav.get("content", "")
    yod_text = yod.get("content", "")
    errors = []

    # Find sentences from background repeated verbatim in discussion
    sentences = [s.strip() for s in re.split(r'[.!?]', vav_text) if len(s.strip()) > 30]
    for sent in sentences:
        if sent in yod_text:
            errors.append(f"כפילות: \"{sent[:60]}...\"")

    return {
        "name": "no_duplication",
        "passed": len(errors) == 0,
        "errors": errors,
        "severity": "warning",
    }


def check_sequential_numbering(blocks: list[dict]) -> dict:
    """בדיקת מספור רציף בין הבלוקים."""
    errors = []
    all_numbers = []

    for block in blocks:
        content = block.get("content", "")
        # Find numbered paragraphs: "1." or "**1.**" or "**1.**"
        numbers = re.findall(r"^(?:\*\*)?(\d+)\.(?:\*\*)?", content, re.MULTILINE)
        all_numbers.extend(int(n) for n in numbers)

    if all_numbers:
        # Check for gaps
        sorted_nums = sorted(set(all_numbers))
        for i in range(1, len(sorted_nums)):
            if sorted_nums[i] - sorted_nums[i-1] > 1:
                errors.append(f"פער במספור: {sorted_nums[i-1]} → {sorted_nums[i]}")
        # Check starts at 1
        if sorted_nums and sorted_nums[0] != 1:
            errors.append(f"מספור מתחיל מ-{sorted_nums[0]} במקום 1")

    return {
        "name": "sequential_numbering",
        "passed": len(errors) == 0,
        "errors": errors,
        "severity": "warning",
    }


async def check_citation_resolution(case_id: UUID, decision_id=None) -> dict:
    """GAP-20/INV-AUD3: every cited case_law_id must resolve to the corpus.

    Reads case_law_ids from the decision's write_block audit provenance and
    verifies each resolves. Unresolvable → NON-BLOCKING warning + audit event.
    """
    from legal_mcp.services import audit

    rows = await audit.get_audit_log(case_id=case_id, action="write_block", limit=200)
    ids = set()
    for r in rows:
        details = r.get("details") or {}
        if isinstance(details, str):
            try:
                details = json.loads(details)
            except (ValueError, TypeError):
                details = {}
        for raw in (details.get("sources") or {}).get("case_law_ids", []):
            try:
                ids.add(UUID(str(raw)))
            except (ValueError, TypeError):
                pass

    if not ids:
        return {"name": "citation_resolution", "passed": True, "errors": [], "severity": "warning"}

    res = await db.resolve_citation_case_law_ids(list(ids))
    if not res["unresolved"]:
        return {"name": "citation_resolution", "passed": True, "errors": [], "severity": "warning"}

    await audit.log_action_safe(
        "citation_unresolved", case_id=case_id,
        details={"unresolved": [str(x) for x in res["unresolved"]]},
    )
    return {
        "name": "citation_resolution",
        "passed": False,
        "severity": "warning",
        "errors": [
            f"{len(res['unresolved'])} ציטוטים אינם פתירים לקורפוס — דורש אימות יו\"ר",
        ],
    }


# ── Main validation ───────────────────────────────────────────────

async def validate_decision(case_id: UUID) -> dict:
    """הרצת כל בדיקות QA על החלטה.

    Returns:
        dict עם passed (bool), results (list), critical_failures (int)
    """
    case = await db.get_case(case_id)
    if not case:
        raise ValueError(f"Case {case_id} not found")

    decision = await db.get_decision_by_case(case_id)
    if not decision:
        raise ValueError(f"No decision for case {case_id}")

    # Get blocks
    pool = await db.get_pool()
    async with pool.acquire() as conn:
        rows = await conn.fetch(
            """SELECT block_id, block_index, title, content, word_count
               FROM decision_blocks WHERE decision_id = $1
               ORDER BY block_index""",
            UUID(decision["id"]),
        )
    blocks = [dict(r) for r in rows]

    # Get claims
    claims = await db.get_claims(case_id)

    # Determine appeal type + outcome (outcome relaxes claims coverage on full acceptance — #87)
    appeal_type = case.get("appeal_type", "licensing")
    from legal_mcp.services.lessons import canonical_outcome
    outcome = canonical_outcome(decision.get("outcome", "") or "")

    # Run all checks
    # Run sync checks
    results = [
        check_neutral_background(blocks),
    ]
    # Async check: claims coverage with Claude
    results.append(await check_claims_coverage(blocks, claims, outcome))
    # More sync checks
    results.extend([
        check_weight_compliance(blocks, appeal_type),
        check_structural_integrity(blocks),
        check_no_duplication(blocks),
        check_sequential_numbering(blocks),
    ])
    # Async, non-blocking warning: citation→corpus resolution (GAP-20/INV-AUD3)
    results.append(await check_citation_resolution(case_id, decision["id"]))

    critical_failures = sum(1 for r in results if not r["passed"] and r["severity"] == "critical")
    all_passed = all(r["passed"] for r in results)

    # Store results in qa_results table
    async with pool.acquire() as conn:
        # Clear previous results
        await conn.execute(
            "DELETE FROM qa_results WHERE case_id = $1",
            case_id,
        )
        for result in results:
            await conn.execute(
                """INSERT INTO qa_results
                   (decision_id, case_id, check_name, passed, severity, errors, details)
                   VALUES ($1, $2, $3, $4, $5, $6, $7)""",
                UUID(decision["id"]), case_id,
                result["name"], result["passed"], result["severity"],
                json.dumps(result["errors"], ensure_ascii=False),
                "",
            )

    return {
        "passed": all_passed,
        "critical_failures": critical_failures,
        "export_blocked": critical_failures > 0,
        "results": results,
        "total_checks": len(results),
        "passed_checks": sum(1 for r in results if r["passed"]),
    }
