from flask import Flask, request, jsonify
import joblib
from sklearn.naive_bayes import MultinomialNB
from preprocessing import preprocess_text

app = Flask(__name__)

# ==============================================================================
# LOAD MODEL ML & VEKTORISASI (Ditraining Sebelumnya)
# ==============================================================================
# Penjelasan Sidang: 
# Model pkl yang dimuat adalah hasil training sebelumnya agar sistem dapat
# langsung melakukan prediksi tanpa harus melatih ulang model setiap saat.
# 1. model_kategori: Model Naive Bayes untuk menentukan kategori aduan.
# 2. model_prioritas: Model Naive Bayes untuk mendeteksi tingkat prioritas aduan.
# 3. tfidf: Vektoriser TF-IDF untuk merubah kata menjadi nilai bobot numerik.
# ==============================================================================
print("Memuat Model ML...")
model_kategori = joblib.load('model_kategori.pkl')
model_prioritas = joblib.load('model_prioritas.pkl')
tfidf = joblib.load('tfidf.pkl')


# ==============================================================================
# 🔥 DETEKSI KATA KUNCI (Rule-Based Kategori)
# ==============================================================================
# Penjelasan Sidang:
# Merupakan filter pertama (Sistem Hibrida). Jika pengguna menulis kata kunci
# yang sangat eksplisit, sistem langsung mengelompokkannya secara otomatis demi
# efisiensi dan akurasi tinggi pada kata kunci krusial.
# ==============================================================================
def detect_keywords(text):
    text = text.lower()
    hasil = []

    # KEBERSIHAN
    if "sampah" in text or "banjir" in text or "kotor" in text or "limbah" in text:
        hasil.append("kebersihan_lingkungan")

    # INFRASTRUKTUR
    if (
        "jalan rusak" in text or
        "berlubang" in text or
        "jembatan rusak" in text or
        "fasilitas rusak" in text or
        "longsor" in text or
        "ambrol" in text
    ):
        hasil.append("infrastruktur_fasilitas")

    # UTILITAS
    if (
        "lampu jalan" in text or
        "air tidak mengalir" in text or
        "air mati" in text or
        "listrik padam" in text or
        "mati lampu" in text or
        "sinyal" in text
    ):
        hasil.append("layanan_publik_utilitas")

    # KEAMANAN
    if (
        "maling" in text or
        "pencurian" in text or
        "kejahatan" in text or
        "mabuk" in text or
        "balap liar" in text or
        "preman" in text
    ):
        hasil.append("keamanan_ketertiban")

    # ADMINISTRASI
    if (
        "pelayanan" in text or
        "administrasi" in text or
        "lambat" in text or
        "pungli" in text or
        "berbelit" in text
    ):
        hasil.append("pelayanan_administrasi")

    return list(set(hasil))


# ==============================================================================
# 🔥 MULTI KATEGORI MACHINE LEARNING (TF-IDF & Naive Bayes)
# ==============================================================================
# Penjelasan Sidang:
# Fungsi ini memprediksi kategori aduan dengan memisahkan teks pengaduan
# yang mengandung beberapa isu (misal: "jalan berlubang dan lampu padam").
# ==============================================================================
def multi_predict(text):
    # Memecah kalimat berdasarkan konjungsi "dan" atau koma untuk memprediksi multikategori
    parts = text.lower().replace(",", " dan ").split(" dan ")
    hasil = []
    scores = []

    for p in parts:
        if len(p.strip()) < 3:
            continue

        # 1. Melakukan Text Preprocessing (Case folding, Tokenizing, Stopwords, Stemming)
        p_clean = preprocess_text(p)
        if not p_clean:
            p_clean = p
            
        # 2. Vektorisasi TF-IDF: Mengubah teks bersih menjadi nilai numerik berdasarkan kamus TF-IDF
        X_input = tfidf.transform([p_clean])
        
        # 3. Prediksi Naive Bayes: Menghitung probabilitas kelas menggunakan Multinomial Naive Bayes
        kategori = model_kategori.predict(X_input)[0]
        
        # 4. Confidence Score: Mengambil probabilitas posterior kelas terpilih (predict_proba)
        probs = model_kategori.predict_proba(X_input)[0]
        class_idx = list(model_kategori.classes_).index(kategori)
        score = probs[class_idx]
        
        hasil.append(kategori)
        scores.append(score)

    avg_score = sum(scores) / len(scores) if scores else 1.0
    return list(set(hasil)), avg_score


# ==============================================================================
# 🔥 DETEKSI PRIORITAS (Rule-Based)
# ==============================================================================
# Penjelasan Sidang:
# Secara otomatis menaikkan prioritas aduan menjadi "Penting" jika mengandung
# kata-kata yang membutuhkan penanganan darurat (darurat kebencanaan/kriminalitas).
# ==============================================================================
def detect_priority(text):
    text = text.lower()

    if (
        "rusak" in text or
        "parah" in text or
        "berbahaya" in text or
        "banjir" in text or
        "maling" in text or
        "pencurian" in text or
        "meledak" in text or
        "kebakaran" in text
    ):
        return "penting"

    return None


# ==============================================================================
# 🔥 API ENDPOINT PREDICT (/predict)
# ==============================================================================
# Penjelasan Sidang:
# Endpoint utama yang dipanggil oleh Laravel ketika ada aduan masuk.
# Menerapkan pendekatan hibrida: Rule-Based dahulu, jika tidak terdeteksi
# baru menggunakan model Machine Learning (TF-IDF + Naive Bayes).
# ==============================================================================
@app.route('/predict', methods=['POST'])
def predict():
    data = request.get_json()
    text = data['text']

    # Langkah 1: Deteksi kategori dengan Kata Kunci
    kategori = detect_keywords(text)
    confidence = 1.0

    # Langkah 2: Jika kata kunci tidak cocok, gunakan model Machine Learning Naive Bayes
    if not kategori:
        kategori, confidence = multi_predict(text)

    # Langkah 3: Deteksi tingkat prioritas aduan (Penting/Biasa)
    priority_keyword = detect_priority(text)

    if priority_keyword:
        prioritas = priority_keyword
        prio_confidence = 1.0
    else:
        # Gunakan ML model prioritas jika tidak mengandung keyword darurat
        p_clean = preprocess_text(text)
        if not p_clean:
            p_clean = text
            
        X_input = tfidf.transform([p_clean])
        prioritas = model_prioritas.predict(X_input)[0]
        
        # Hitung skor confidence untuk tingkat prioritas aduan
        prio_probs = model_prioritas.predict_proba(X_input)[0]
        prio_class_idx = list(model_prioritas.classes_).index(prioritas)
        prio_confidence = prio_probs[prio_class_idx]

    return jsonify({
        "kategori": kategori,
        "prioritas": prioritas,
        "confidence": round(float(confidence) * 100, 2)
    })


# ==============================================================================
# API ENDPOINT EVALUASI (/evaluate)
# ==============================================================================
# Penjelasan Sidang:
# Endpoint krusial untuk verifikasi ilmiah di sidang skripsi.
# 1. Mengambil dataset prapemrosesan terstandarisasi.
# 2. Membagi data (Split) menjadi 80% Data Training dan 20% Data Testing.
# 3. Melatih model evaluasi secara lokal menggunakan Multinomial Naive Bayes.
#    Parameter alpha=0.01 (Laplace Smoothing) disematkan untuk menghindari nilai
#    probabilitas nol pada kata baru yang belum pernah muncul di data training.
# 4. Menghasilkan metrik evaluasi: Akurasi, Precision, Recall, F1-Score, dan Confusion Matrix.
# ==============================================================================
@app.route('/evaluate', methods=['GET'])
def evaluate():
    from sklearn.metrics import classification_report, confusion_matrix, accuracy_score
    from sklearn.model_selection import train_test_split
    from preprocessed_dataset import preprocessed_data
    
    print("Menghitung evaluasi model...")
    
    texts = []
    categories = []
    for text, clean_text, cat, prio in preprocessed_data:
        texts.append(clean_text)
        categories.append(cat)
        
    # Vektoriser TF-IDF untuk merubah seluruh teks dataset menjadi vektor angka
    X = tfidf.transform(texts)
    
    # Membagi dataset secara acak menjadi 80% Data Training & 20% Data Testing
    # random_state dikunci agar pembagian data konsisten di setiap pengujian
    X_train, X_test, y_train, y_test = train_test_split(
        X, categories, test_size=0.2, random_state=229
    )
    
    # Menggunakan Multinomial Naive Bayes dengan Laplace Smoothing (alpha=0.01)
    eval_model = MultinomialNB(alpha=0.01)
    eval_model.fit(X_train, y_train)
    
    # Memprediksi hasil kategori untuk data testing (20%)
    y_pred = eval_model.predict(X_test)
    
    # Perhitungan Metrik Akurasi, Laporan Klasifikasi (Precision, Recall, F1-Score), dan Confusion Matrix
    acc = accuracy_score(y_test, y_pred)
    report = classification_report(y_test, y_pred, output_dict=True)
    matrix = confusion_matrix(y_test, y_pred).tolist()
    classes = list(eval_model.classes_)
    
    return jsonify({
        "accuracy": round(acc * 100, 2),
        "report": report,
        "matrix": matrix,
        "classes": classes
    })


# ==============================================================================
# RUN API FLASK
# ==============================================================================
if __name__ == '__main__':
    app.run(debug=True, port=5000)