#!/usr/bin/env python3
"""
Calibration Script: Static Fallback Threshold Calculation
==========================================================
Menghitung threshold fallback menggunakan Z-Score (Mean + 3*StdDev) × Section_Multiplier
dari 66 node Literature dengan project_id = "workspace-dev-01"

Output: JSON dictionary dengan threshold per pilar
"""

import os
import sys
import json
import numpy as np
from dotenv import load_dotenv
from neo4j import GraphDatabase

# Load environment variables
load_dotenv(override=True)

# Neo4j Configuration
NEO4J_URI = os.getenv("NEO4J_URI", "")
NEO4J_USERNAME = os.getenv("NEO4J_USERNAME", "")
NEO4J_PASSWORD = os.getenv("NEO4J_PASSWORD", "")

# Constants
PILLARS = ["background", "methodology", "gap", "objective", "futurework"]
SAMPLE_SIZE = 500  # Jumlah pasangan per pilar

# Section-Specific Multipliers (dari diskusi dengan user)
SECTION_MULTIPLIERS = {
    "background": 1.0,
    "methodology": 1.0,
    "objective": 1.05,
    "gap": 1.15,
    "futurework": 1.05
}

PROJECT_ID = "workspace-dev-01"


def cosine_similarity(vec1: list, vec2: list) -> float:
    """Hitung cosine similarity antara dua vektor"""
    if not vec1 or not vec2 or len(vec1) != len(vec2):
        return 0.0
    
    vec1_np = np.array(vec1)
    vec2_np = np.array(vec2)
    
    dot_product = np.dot(vec1_np, vec2_np)
    norm1 = np.linalg.norm(vec1_np)
    norm2 = np.linalg.norm(vec2_np)
    
    if norm1 == 0 or norm2 == 0:
        return 0.0
    
    return float(dot_product / (norm1 * norm2))


def sample_node_pairs(driver, pillar: str, sample_size: int) -> list:
    """
    Ambil sample_size pasangan node acak untuk pilar tertentu
    Returns: list of (vec1, vec2) tuples
    """
    embedding_prop = f"embedding_{pillar}"
    
    cypher = f"""
    MATCH (n1:Literature), (n2:Literature)
    WHERE n1.project_id = $project_id 
      AND n2.project_id = $project_id
      AND elementId(n1) < elementId(n2)
      AND n1.{embedding_prop} IS NOT NULL
      AND n2.{embedding_prop} IS NOT NULL
    WITH n1, n2, rand() AS r
    ORDER BY r
    LIMIT $sample_size
    RETURN n1.{embedding_prop} AS vec1, n2.{embedding_prop} AS vec2
    """
    
    pairs = []
    with driver.session() as session:
        result = session.run(cypher, project_id=PROJECT_ID, sample_size=sample_size)
        for record in result:
            pairs.append((record["vec1"], record["vec2"]))
    
    return pairs


def calculate_threshold_for_pillar(driver, pillar: str) -> dict:
    """
    Kalkulasi threshold untuk satu pilar menggunakan Z-Score
    Returns: dict dengan mean, stddev, z_score, multiplier, final_threshold
    """
    print(f"\n[{pillar.upper()}] Mengambil {SAMPLE_SIZE} pasangan acak...")
    
    pairs = sample_node_pairs(driver, pillar, SAMPLE_SIZE)
    
    if len(pairs) == 0:
        print(f"  ⚠️  Tidak ada pasangan valid untuk pilar {pillar}")
        return None
    
    print(f"  ✓ Berhasil mengambil {len(pairs)} pasangan")
    print(f"  → Menghitung cosine similarity...")
    
    similarities = [cosine_similarity(vec1, vec2) for vec1, vec2 in pairs]
    
    # Statistik
    mean = float(np.mean(similarities))
    stddev = float(np.std(similarities))
    
    # Z-Score: μ + 3σ
    z_score = mean + (3 * stddev)
    
    # Apply multiplier
    multiplier = SECTION_MULTIPLIERS[pillar]
    final_threshold = z_score * multiplier
    
    print(f"  → Mean (μ): {mean:.4f}")
    print(f"  → StdDev (σ): {stddev:.4f}")
    print(f"  → Z-Score (μ + 3σ): {z_score:.4f}")
    print(f"  → Multiplier: {multiplier}x")
    print(f"  ✓ Final Threshold: {final_threshold:.4f}")
    
    return {
        "pillar": pillar,
        "sample_count": len(pairs),
        "mean": round(mean, 4),
        "stddev": round(stddev, 4),
        "z_score": round(z_score, 4),
        "multiplier": multiplier,
        "final_threshold": round(final_threshold, 4)
    }


def main():
    print("=" * 70)
    print("CALIBRATION: Static Fallback Threshold Calculation")
    print("=" * 70)
    print(f"Project ID: {PROJECT_ID}")
    print(f"Sample Size: {SAMPLE_SIZE} pasangan per pilar")
    print(f"Z-Score Formula: (μ + 3σ) × Section_Multiplier")
    print("=" * 70)
    
    # Validasi credentials
    if not all([NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD]):
        print("❌ Neo4j credentials tidak lengkap di .env")
        sys.exit(1)
    
    # Koneksi ke Neo4j
    print("\n[KONEKSI] Menghubungkan ke Neo4j...")
    driver = GraphDatabase.driver(NEO4J_URI, auth=(NEO4J_USERNAME, NEO4J_PASSWORD))
    
    try:
        # Verifikasi jumlah node
        with driver.session() as session:
            result = session.run(
                "MATCH (n:Literature) WHERE n.project_id = $project_id RETURN count(n) AS total",
                project_id=PROJECT_ID
            )
            total_nodes = result.single()["total"]
            print(f"  ✓ Terhubung! Total node dengan project_id '{PROJECT_ID}': {total_nodes}")
            
            if total_nodes < 10:
                print(f"  ⚠️  WARNING: Hanya {total_nodes} node, hasil mungkin tidak representatif")
        
        # Kalkulasi threshold per pilar
        results = {}
        for pillar in PILLARS:
            result = calculate_threshold_for_pillar(driver, pillar)
            if result:
                results[pillar] = result
        
        # Output JSON
        print("\n" + "=" * 70)
        print("HASIL KALIBRASI (JSON Format)")
        print("=" * 70)
        
        threshold_dict = {
            pillar: results[pillar]["final_threshold"] 
            for pillar in PILLARS if pillar in results
        }
        
        print(json.dumps(threshold_dict, indent=2))
        
        # Output detail lengkap
        print("\n" + "=" * 70)
        print("DETAIL LENGKAP")
        print("=" * 70)
        print(json.dumps(results, indent=2))
        
        # Validasi rentang
        print("\n" + "=" * 70)
        print("VALIDASI")
        print("=" * 70)
        for pillar, threshold in threshold_dict.items():
            status = "✓ OK" if 0.45 <= threshold <= 0.85 else "⚠️  OUT OF RANGE"
            print(f"  {pillar}: {threshold:.4f} {status}")
        
    finally:
        driver.close()
        print("\n[SELESAI] Koneksi Neo4j ditutup.")


if __name__ == "__main__":
    main()
