"""
edge_reasoning_quality_gate_check.py
====================================
Quality gate checker untuk R2-12 (Edge Reasoning Quality Gate Test).

Tujuan:
- Memverifikasi reasoning edge tidak generik.
- Memastikan reasoning mengandung elemen substantif yang membedakan pasangan paper.

Cara menjalankan:
  python edge_reasoning_quality_gate_check.py
"""

import os
import re
from dotenv import load_dotenv
from neo4j import GraphDatabase


load_dotenv()

NEO4J_URI = os.getenv("NEO4J_URI", "")
NEO4J_USERNAME = os.getenv("NEO4J_USERNAME", "neo4j")
NEO4J_PASSWORD = os.getenv("NEO4J_PASSWORD", "")

MAX_REPORT_ROWS = 20

# Frasa yang cenderung generik jika tidak dibarengi bukti substantif.
GENERIC_PATTERNS = [
    r"\bsama[- ]?sama membahas\b",
    r"\bsama[- ]?sama membicarakan\b",
    r"\bkeduanya (membahas|berfokus|menjelaskan)\b",
    r"\bmemiliki kemiripan\b",
    r"\bsecara umum\b",
    r"\btopik (yang )?serupa\b",
    r"\brelevan satu sama lain\b",
    r"\bberkaitan\b",
]

# Indikator evidence substantif lintas pilar (metode, objective, evaluasi, gap, dsb).
SPECIFICITY_KEYWORDS = {
    "metode",
    "metodologi",
    "pendekatan",
    "algoritma",
    "model",
    "arsitektur",
    "pipeline",
    "eksperimen",
    "evaluasi",
    "metrik",
    "akurasi",
    "precision",
    "recall",
    "f1",
    "objective",
    "tujuan",
    "gap",
    "keterbatasan",
    "future work",
    "dataset",
    "retrieval",
    "rag",
    "ragas",
    "embedding",
    "vector",
    "knowledge graph",
    "neo4j",
    "semantic",
    "benchmark",
    "baseline",
}

# Kata penghubung yang menunjukkan adanya argumentasi/evidence chain.
EVIDENCE_CONNECTORS = {
    "karena",
    "sebab",
    "dengan",
    "melalui",
    "menggunakan",
    "menunjukkan",
    "terlihat",
    "dibuktikan",
    "berdasarkan",
}


def _find_generic_matches(text: str) -> list[str]:
    hits = []
    for pattern in GENERIC_PATTERNS:
        if re.search(pattern, text, flags=re.IGNORECASE):
            hits.append(pattern)
    return hits


def _count_specific_keywords(text: str) -> int:
    lowered = text.lower()
    return sum(1 for kw in SPECIFICITY_KEYWORDS if kw in lowered)


def _has_evidence_connector(text: str) -> bool:
    lowered = text.lower()
    return any(conn in lowered for conn in EVIDENCE_CONNECTORS)


def evaluate_reasoning_quality(reasoning: str) -> tuple[bool, list[str], dict]:
    """Return (passed, reasons, features) for a reasoning text."""
    reasons = []
    text = (reasoning or "").strip()
    lowered = text.lower()

    if not text:
        reasons.append("empty-reasoning")
        return False, reasons, {"word_count": 0, "specificity_hits": 0, "generic_hits": 0, "has_connector": False}

    word_count = len(re.findall(r"\w+", text, flags=re.UNICODE))
    specificity_hits = _count_specific_keywords(text)
    generic_hits = _find_generic_matches(text)
    has_connector = _has_evidence_connector(text)

    # Fallback jawaban ini menandakan bukti tidak cukup kuat, tidak lolos quality gate.
    if "bukti kemiripan substansial tidak cukup kuat" in lowered:
        reasons.append("insufficient-evidence-fallback")

    # Reasoning terlalu pendek cenderung tidak substantif.
    if word_count < 8:
        reasons.append("too-short")

    # Harus ada minimal satu evidence keyword domain/metode/tujuan/gap/evaluasi.
    if specificity_hits < 1:
        reasons.append("missing-specific-evidence")

    # Frasa generik tanpa penguat evidence ditolak.
    if generic_hits and not has_connector:
        reasons.append("generic-without-evidence")

    passed = len(reasons) == 0
    features = {
        "word_count": word_count,
        "specificity_hits": specificity_hits,
        "generic_hits": len(generic_hits),
        "has_connector": has_connector,
    }
    return passed, reasons, features


def main() -> int:
    if not all([NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD]):
        print("❌ ERROR: Kredensial Neo4j belum lengkap di file .env")
        return 1

    driver = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USERNAME, NEO4J_PASSWORD))

    cypher = """
    MATCH (a:Literature)-[r]->(b:Literature)
    WHERE type(r) STARTS WITH 'SIMILAR_'
    RETURN
      elementId(r) AS edge_id,
      type(r) AS rel_type,
      a.title AS source_title,
      b.title AS target_title,
      coalesce(r.reasoning, r.summary, '') AS reasoning,
      coalesce(r.matched_pillars, []) AS matched_pillars,
      coalesce(r.gate, 'legacy') AS gate,
      r.score AS score
    ORDER BY rel_type, score DESC
    """

    with driver.session() as session:
        records = list(session.run(cypher))

    driver.close()

    print("=" * 78)
    print("R2-12 Edge Reasoning Quality Gate Check")
    print("=" * 78)
    print(f"Total edge SIMILAR_* diperiksa : {len(records)}")

    if not records:
        print("\n⚠️  WARNING: Tidak ada edge SIMILAR_* untuk dievaluasi.")
        return 0

    passed_count = 0
    failed_rows = []

    for rec in records:
        passed, reasons, features = evaluate_reasoning_quality(rec["reasoning"])
        if passed:
            passed_count += 1
            continue
        failed_rows.append({
            "edge_id": rec["edge_id"],
            "rel_type": rec["rel_type"],
            "score": rec["score"] or 0.0,
            "source_title": rec["source_title"] or "",
            "target_title": rec["target_title"] or "",
            "reasons": reasons,
            "features": features,
            "reasoning": (rec["reasoning"] or "").strip(),
        })

    failed_count = len(failed_rows)
    pass_rate = (passed_count / len(records)) * 100.0 if records else 0.0

    print(f"Edge PASS quality gate          : {passed_count}")
    print(f"Edge FAIL quality gate          : {failed_count}")
    print(f"Pass rate                       : {pass_rate:.2f}%")

    if failed_rows:
        print("\n❌ FAIL: Ditemukan edge dengan reasoning yang belum memenuhi quality gate.")
        print(f"Menampilkan maksimal {MAX_REPORT_ROWS} edge gagal:\n")
        for idx, row in enumerate(failed_rows[:MAX_REPORT_ROWS], 1):
            reason_text = ",".join(row["reasons"])
            feat = row["features"]
            print(
                f"[{idx}] {row['rel_type']} | score={row['score']:.4f} | fail={reason_text} | "
                f"wc={feat['word_count']} | spec={feat['specificity_hits']} | "
                f"generic={feat['generic_hits']} | connector={feat['has_connector']}"
            )
            print(f"    Source: {row['source_title']}")
            print(f"    Target: {row['target_title']}")
            snippet = row["reasoning"]
            if len(snippet) > 220:
                snippet = snippet[:217] + "..."
            print(f"    Reasoning: {snippet}\n")
        return 2

    print("\n✅ PASS: Semua edge reasoning memenuhi quality gate non-generik dan substantif.")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
