import os
import json
import asyncio
import logging
import httpx
import pandas as pd
from dotenv import load_dotenv

# Ragas & Langchain
from ragas import evaluate, EvaluationDataset, RunConfig
from ragas.metrics import Faithfulness
from ragas.llms import LangchainLLMWrapper
from langchain_openai import ChatOpenAI

# Import Local Retrievers to fetch contexts for evaluation
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from app.services.chatbot.graph_retriever import GraphRetriever
from app.services.chatbot.vector_retriever import VectorRetriever

# Konfigurasi Logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

load_dotenv(override=True)

API_URL = "http://localhost:8000/api/chat/stream"

async def get_chatbot_response(query: str, active_hash: str, session_id: str) -> str:
    """Mengambil jawaban dari endpoint streaming chatbot."""
    payload = {
        "query": query,
        "session_id": session_id,
        "active_hash": active_hash
    }
    
    max_retries = 3
    for attempt in range(max_retries):
        full_response = ""
        try:
            async with httpx.AsyncClient() as client:
                async with client.stream("POST", API_URL, json=payload, timeout=120.0) as response:
                    response.raise_for_status()
                    async for chunk in response.aiter_text():
                        full_response += chunk
            
            # Deteksi error 429 dari Google API yang bocor ke respon teks chatbot
            if "429 RESOURCE_EXHAUSTED" in full_response or "Quota exceeded" in full_response:
                wait_time = 45 * (attempt + 1)
                logger.warning(f"[WARN] Terdeteksi Rate Limit Gemini (429). Menunggu {wait_time} detik sebelum mencoba ulang...")
                await asyncio.sleep(wait_time)
                continue # Coba lagi
                
            return full_response
        except Exception as e:
            logger.error(f"[ERROR] Gagal mengambil respon dari API: {e}")
            await asyncio.sleep(5)
            
    return "Gagal mendapatkan respon setelah beberapa kali percobaan akibat Rate Limit."

async def get_chatbot_contexts(active_hash: str) -> list[str]:
    """Mengambil konteks (chunks) yang kira-kira dilihat oleh chatbot untuk evaluasi Ragas."""
    graph_retriever = GraphRetriever()
    vector_retriever = VectorRetriever(table_name="document_chunks")
    
    await graph_retriever.connect()
    
    target_hashes = [active_hash]
    try:
        # Ambil tetangga dari Neo4j
        neighbors = await graph_retriever.get_relevant_neighbors(active_hash, max_nodes=2)
        if neighbors:
            target_hashes.extend([n["hash"] for n in neighbors if n.get("hash")])
    except Exception as e:
        logger.error(f"[ERROR] Graph retrieval error: {e}")
    finally:
        await graph_retriever.close()
        
    # Ambil konten teks dari Supabase
    raw_chunks = vector_retriever.fetch_chunks_by_hashes(target_hashes)
    return [chunk.get("content", "") for chunk in raw_chunks if chunk.get("content")]

async def build_evaluation_dataset(dataset_path: str) -> EvaluationDataset:
    """Membangun dataset Ragas dari file JSON dan respon API."""
    with open(dataset_path, "r") as f:
        raw_data = json.load(f)
        
    samples = []
    logger.info(f"[INFO] Membangun dataset untuk {len(raw_data)} pertanyaan...")
    
    for i, item in enumerate(raw_data):
        query = item.get("query")
        active_hash = item.get("active_hash")
        reference = item.get("ground_truth")
        session_id = f"eval-session-{i}"
        
        logger.info(f"[{i+1}/{len(raw_data)}] Memproses: '{query}'")
        
        # 1. Dapatkan Jawaban AI (HTTP Runner)
        response_text = await get_chatbot_response(query, active_hash, session_id)
        
        # 2. Dapatkan Konteks (Reference)
        contexts = await get_chatbot_contexts(active_hash)
        
        if not contexts:
            logger.warning(f"[WARN] Konteks kosong untuk hash {active_hash}. Menggunakan placeholder.")
            contexts = ["Konteks referensi tidak ditemukan di database."]

        samples.append({
            "user_input": query,
            "response": response_text,
            "retrieved_contexts": contexts,
            "reference": reference
        })
        
    return EvaluationDataset.from_list(samples)

def main():
    print("="*50)
    print("[INFO] MEMULAI EVALUASI FINAL: FAITHFULNESS ONLY")
    print("[INFO] (Menghindari Error Embeddings & Timeout)")
    print("="*50)
    
    dashscope_api_key = os.getenv("DASHSCOPE_API_KEY")
    if not dashscope_api_key:
        logger.error("[ERROR] DASHSCOPE_API_KEY tidak ditemukan di .env!")
        return

    # 1. Konfigurasi Juri Qwen (DashScope)
    # Gunakan model Qwen-Plus yang sudah terbukti bekerja (200 OK)
    qwen_llm = ChatOpenAI(
        api_key=dashscope_api_key,
        base_url="https://dashscope-intl.aliyuncs.com/compatible-mode/v1",
        model="qwen-plus"
    )
    evaluator_llm = LangchainLLMWrapper(qwen_llm)
    logger.info("[SUCCESS] Juri Qwen berhasil diinisialisasi.")
    
    # 2. Bangun Dataset
    dataset_path = os.path.join(os.path.dirname(__file__), "..", "data", "eval_dataset.json")
    
    try:
        loop = asyncio.get_event_loop()
    except RuntimeError:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        
    eval_dataset = loop.run_until_complete(build_evaluation_dataset(dataset_path))
    logger.info("[SUCCESS] Dataset Evaluasi berhasil dibangun.")
    
    # 3. Eksekusi Ragas (HANYA FAITHFULNESS)
    # Metrik ini hanya butuh LLM (Qwen), tidak butuh Embeddings (Google/OpenAI).
    # Ini menjamin 0% Error 404/401 pada tahap penilaian.
    metrics = [
        Faithfulness(llm=evaluator_llm)
    ]
    
    # RunConfig untuk mencegah TimeoutError
    config = RunConfig(timeout=180, max_workers=1)
    
    logger.info("[WAIT] Menjalankan penilaian. Harap tunggu...")
    results = evaluate(
        dataset=eval_dataset,
        metrics=metrics,
        llm=evaluator_llm,
        run_config=config
    )
    
    # 4. Ekspor Hasil
    df = results.to_pandas()
    print("\n" + "="*50)
    print("[RESULT] LAPORAN EVALUASI (ANTI-HALUSINASI)")
    print("="*50)
    # Tampilkan kolom yang ada
    print(df[['user_input', 'faithfulness']].to_markdown())
    
    results_dir = os.path.join(os.path.dirname(__file__), "..", "results")
    os.makedirs(results_dir, exist_ok=True)
    out_path = os.path.join(results_dir, "evaluation_results_final.csv")
    
    df.to_csv(out_path, index=False)
    logger.info(f"[SUCCESS] Laporan disimpan ke: {out_path}")
    print("="*50)

if __name__ == "__main__":
    if sys.platform == 'win32':
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
    main()
