from flask import Blueprint, render_template, redirect, url_for, flash, request, send_file, jsonify
from flask_login import login_required, current_user
from functools import wraps
from extensions import db
from models import User, ModelInfo, Prediction, WordCount
from utils.predictor import predict, extract_wordcount_from_tfidf
from sqlalchemy import func
import pandas as pd
import io
import os
import json
from datetime import datetime
from uuid import uuid4

admin_bp = Blueprint('admin', __name__)


def admin_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        if not current_user.is_authenticated or current_user.role != 'admin':
            flash('Akses ditolak. Halaman khusus admin.', 'danger')
            return redirect(url_for('auth.login'))
        return f(*args, **kwargs)
    return decorated


# ── Dashboard ──────────────────────────────────────────────────────────────
@admin_bp.route('/dashboard')
@login_required
@admin_required
def dashboard():
    total_users    = User.query.filter_by(role='user').count()
    total_prediksi = Prediction.query.count()
    total_kata     = WordCount.query.count()

    dist_pred = [
        (row[0], row[1]) for row in db.session.query(
            Prediction.label_hasil, func.count(Prediction.id)
        ).group_by(Prediction.label_hasil).all()
    ]

    models    = ModelInfo.query.all()
    top_words = WordCount.query.order_by(WordCount.frekuensi.desc()).limit(10).all()

    from datetime import datetime, timedelta
    tujuh_hari = [(datetime.utcnow() - timedelta(days=i)).strftime('%Y-%m-%d') for i in range(6, -1, -1)]
    pred_per_hari = []
    for tgl in tujuh_hari:
        count = Prediction.query.filter(
            func.date(Prediction.created_at) == tgl
        ).count()
        pred_per_hari.append({'tanggal': tgl, 'total': count})

    return render_template('admin/dashboard.html',
        total_users=total_users,
        total_prediksi=total_prediksi,
        total_kata=total_kata,
        dist_pred=dist_pred,
        models=models,
        top_words=top_words,
        pred_per_hari=pred_per_hari,
    )


# ── Model Info ─────────────────────────────────────────────────────────────
@admin_bp.route('/model')
@login_required
@admin_required
def model():
    models = ModelInfo.query.all()
    
    # Scan folder models/ untuk list folder yang tersedia
    base_dir     = os.path.dirname(os.path.dirname(__file__))
    models_dir   = os.path.join(base_dir, 'models')
    folder_list  = []
    
    if os.path.exists(models_dir):
        for folder in os.listdir(models_dir):
            folder_path = os.path.join(models_dir, folder)
            if os.path.isdir(folder_path):
                # Deteksi tipe model dari isi folder
                has_bert  = os.path.exists(os.path.join(folder_path, 'config.json'))
                has_nb    = os.path.exists(os.path.join(folder_path, 'nb_model.pkl'))
                has_eval  = os.path.exists(os.path.join(folder_path, 'eval_results.json'))
                
                tipe = 'indobert' if has_bert else ('naive_bayes' if has_nb else 'unknown')
                folder_list.append({
                    'nama'    : folder,
                    'path'    : f'models/{folder}',
                    'tipe'    : tipe,
                    'has_eval': has_eval
                })
    
    return render_template('admin/model.html', models=models, folder_list=folder_list)


@admin_bp.route('/model/load-folder', methods=['POST'])
@login_required
@admin_required
def load_folder():
    """Load model dari folder yang dipilih — otomatis baca metrics & simpan ke DB."""
    path       = request.form.get('path_folder', '').strip()
    is_default = request.form.get('is_default') == 'on'
    
    if not path:
        flash('Pilih folder model dulu!', 'danger')
        return redirect(url_for('admin.model'))
    
    base_dir  = os.path.dirname(os.path.dirname(__file__))
    full_path = os.path.join(base_dir, path)
    
    if not os.path.exists(full_path):
        flash(f'Folder {path} tidak ditemukan!', 'danger')
        return redirect(url_for('admin.model'))
    
    # Deteksi tipe model
    has_bert = os.path.exists(os.path.join(full_path, 'config.json'))
    has_nb   = os.path.exists(os.path.join(full_path, 'nb_model.pkl'))
    
    if has_bert:
        tipe      = 'indobert'
        nama_model = 'IndoBERT'
    elif has_nb:
        tipe      = 'naive_bayes'
        nama_model = 'Naive Bayes'
    else:
        flash('Folder tidak dikenali sebagai model IndoBERT atau Naive Bayes!', 'danger')
        return redirect(url_for('admin.model'))
    
    # Baca eval_results.json kalau ada
    eval_path = os.path.join(full_path, 'eval_results.json')
    accuracy = precision = recall = f1 = None
    
    if os.path.exists(eval_path):
        with open(eval_path, 'r') as f:
            data = json.load(f)
        accuracy  = round(data.get('accuracy',  data.get('eval_accuracy',  0)) * 100, 2)
        precision = round(data.get('precision', data.get('eval_precision', 0)) * 100, 2)
        recall    = round(data.get('recall',    data.get('eval_recall',    0)) * 100, 2)
        f1        = round(data.get('f1_score',  data.get('eval_f1',        data.get('f1', 0))) * 100, 2)
    else:
        flash(f'⚠️ File eval_results.json tidak ditemukan di {path}. Isi metrics manual.', 'warning')
        return redirect(url_for('admin.model'))
    
    # Simpan atau update ke DB
    if is_default:
        ModelInfo.query.update({'is_default': False})
    
    existing = ModelInfo.query.filter_by(tipe=tipe).first()
    if existing:
        existing.accuracy    = accuracy
        existing.precision   = precision
        existing.recall      = recall
        existing.f1_score    = f1
        existing.path_folder = path
        if is_default:
            existing.is_default = True
    else:
        m = ModelInfo(nama_model=nama_model, tipe=tipe, accuracy=accuracy,
                      precision=precision, recall=recall, f1_score=f1,
                      path_folder=path, is_default=is_default)
        db.session.add(m)
    
    db.session.commit()
    flash(f'✅ Model {nama_model} dari folder {path} berhasil dimuat! (Accuracy: {accuracy}%)', 'success')
    return redirect(url_for('admin.model'))


@admin_bp.route('/model/baca-eval', methods=['POST'])
@login_required
@admin_required
def baca_eval():
    """API endpoint: baca eval_results.json dari folder model, return metrics otomatis."""
    path = request.json.get('path_folder', '').strip()
    if not path:
        return jsonify({'error': 'Path kosong'}), 400

    base_dir   = os.path.dirname(os.path.dirname(__file__))
    eval_path  = os.path.join(base_dir, path, 'eval_results.json')

    if not os.path.exists(eval_path):
        return jsonify({'error': f'File eval_results.json tidak ditemukan di {path}'}), 404

    try:
        with open(eval_path, 'r') as f:
            data = json.load(f)
        return jsonify({
            'accuracy' : round(data.get('accuracy',  data.get('eval_accuracy',  0)) * 100, 2),
            'precision': round(data.get('precision', data.get('eval_precision', 0)) * 100, 2),
            'recall'   : round(data.get('recall',    data.get('eval_recall',    0)) * 100, 2),
            'f1_score' : round(data.get('f1',        data.get('eval_f1',        data.get('f1_score', 0))) * 100, 2),
        })
    except Exception as e:
        return jsonify({'error': str(e)}), 500


@admin_bp.route('/model/simpan', methods=['POST'])
@login_required
@admin_required
def simpan_model():
    nama       = request.form.get('nama_model')
    tipe       = request.form.get('tipe')
    accuracy   = request.form.get('accuracy', type=float)
    precision  = request.form.get('precision', type=float)
    recall     = request.form.get('recall', type=float)
    f1         = request.form.get('f1_score', type=float)
    path       = request.form.get('path_folder')
    is_default = request.form.get('is_default') == 'on'

    # Validasi — pastikan tidak ada yang None
    if None in [accuracy, precision, recall, f1]:
        flash('Semua field metrics (accuracy, precision, recall, F1) wajib diisi!', 'danger')
        return redirect(url_for('admin.model'))

    # Kalau dijadikan default, reset semua model lain dulu
    if is_default:
        ModelInfo.query.update({'is_default': False})

    existing = ModelInfo.query.filter_by(tipe=tipe).first()
    if existing:
        existing.accuracy    = accuracy
        existing.precision   = precision
        existing.recall      = recall
        existing.f1_score    = f1
        existing.path_folder = path
        if is_default:
            existing.is_default = True
    else:
        m = ModelInfo(nama_model=nama, tipe=tipe, accuracy=accuracy,
                      precision=precision, recall=recall, f1_score=f1,
                      path_folder=path, is_default=is_default)
        db.session.add(m)

    db.session.commit()
    flash(f'Informasi model {nama} berhasil disimpan.', 'success')
    return redirect(url_for('admin.model'))


@admin_bp.route('/model/set-default/<int:id>', methods=['POST'])
@login_required
@admin_required
def set_default_model(id):
    # Reset semua, lalu set yang dipilih
    ModelInfo.query.update({'is_default': False})
    m = ModelInfo.query.get_or_404(id)
    m.is_default = True
    db.session.commit()
    flash(f'Model {m.nama_model} dijadikan default.', 'success')
    return redirect(url_for('admin.model'))


# ── Prediksi Massal (Admin) ────────────────────────────────────────────────
def _bulk_export_dir():
    base_dir = os.path.dirname(os.path.dirname(__file__))
    export_dir = os.path.join(base_dir, 'exports')
    os.makedirs(export_dir, exist_ok=True)
    return export_dir


def _load_bulk_result_panel(filename=None):
    """Kompatibilitas lama: ambil satu metadata hasil prediksi massal terakhir."""
    history = _load_bulk_results_history()
    if filename:
        filename = os.path.basename(filename)
        for item in history:
            if item.get('hasil_file') == filename:
                return item
        return None
    return history[0] if history else None


def _load_bulk_results_history():
    """Ambil semua riwayat file hasil prediksi massal dari folder exports/.
    Setiap file CSV memiliki metadata JSON dengan nama yang sama.
    Daftar diurutkan dari file terbaru ke terlama agar bisa ditampilkan sebagai tabel.
    """
    export_dir = _bulk_export_dir()
    history = []

    try:
        candidates = [
            f for f in os.listdir(export_dir)
            if f.startswith('hasil_prediksi_bulk_') and f.endswith('.csv')
        ]
    except FileNotFoundError:
        return []

    candidates.sort(key=lambda f: os.path.getmtime(os.path.join(export_dir, f)), reverse=True)

    for filename in candidates:
        csv_path = os.path.join(export_dir, filename)
        meta_path = os.path.splitext(csv_path)[0] + '.json'

        meta = {
            'hasil_file': filename,
            'total': 0,
            'positif': 0,
            'negatif': 0,
            'netral': 0,
            'model': '-',
            'waktu': datetime.fromtimestamp(os.path.getmtime(csv_path)).strftime('%d/%m/%Y %H:%M:%S'),
        }

        if os.path.exists(meta_path):
            try:
                with open(meta_path, 'r', encoding='utf-8') as f:
                    saved_meta = json.load(f)
                    meta.update(saved_meta)
            except Exception:
                pass

        history.append(meta)

    return history


@admin_bp.route('/prediksi')
@login_required
@admin_required
def prediksi():
    """Admin hanya menggunakan prediksi massal agar hasilnya menjadi data global untuk user."""
    bulk_history = _load_bulk_results_history()
    return render_template('admin/prediksi.html', bulk_history=bulk_history)


@admin_bp.route('/prediksi/bulk', methods=['POST'])
@login_required
@admin_required
def prediksi_bulk():
    file          = request.files.get('file')
    model_choice  = request.form.get('model', 'indobert')

    if not file or file.filename == '':
        flash('Pilih file CSV/Excel.', 'danger')
        return redirect(url_for('admin.prediksi'))

    # Simpan nama file asli yang di-upload admin supaya tabel riwayat mudah dikenali.
    original_filename = os.path.basename(file.filename).strip()

    ext = original_filename.rsplit('.', 1)[-1].lower()
    try:
        df = pd.read_csv(file) if ext == 'csv' else pd.read_excel(file, engine='openpyxl')
    except Exception as e:
        flash(f'Gagal membaca file: {str(e)}', 'danger')
        return redirect(url_for('admin.prediksi'))

    if 'text' not in df.columns:
        flash('File harus memiliki kolom "text".', 'danger')
        return redirect(url_for('admin.prediksi'))

    results = []
    for _, row in df.iterrows():
        teks = str(row['text']).strip()
        if not teks:
            continue

        hasil = predict(teks, model=model_choice)
        p = Prediction(
            user_id=current_user.id,
            komentar=teks,
            label_hasil=hasil['label'],
            confidence=hasil['confidence'],
            prob_positif=hasil['prob_positif'],
            prob_negatif=hasil['prob_negatif'],
            prob_netral=hasil['prob_netral'],
            model_dipakai=model_choice,
            tipe_input='bulk'
        )
        db.session.add(p)
        results.append({
            'text': teks,
            'label': hasil['label'],
            'confidence': hasil['confidence'],
            'prob_positif': hasil['prob_positif'],
            'prob_negatif': hasil['prob_negatif'],
            'prob_netral': hasil['prob_netral'],
            'model': model_choice
        })

    db.session.commit()

    if not results:
        flash('Tidak ada komentar valid yang berhasil diproses.', 'warning')
        return redirect(url_for('admin.prediksi'))

    # Simpan hasil ke folder exports supaya tombol download bisa tetap muncul setelah halaman di-refresh.
    export_dir = _bulk_export_dir()

    filename = f"hasil_prediksi_bulk_{datetime.now().strftime('%Y%m%d_%H%M%S')}_{uuid4().hex[:8]}.csv"
    filepath = os.path.join(export_dir, filename)
    pd.DataFrame(results).to_csv(filepath, index=False, encoding='utf-8-sig')

    label_counts = pd.Series([r['label'] for r in results]).value_counts().to_dict()
    meta = {
        'hasil_file': filename,
        'original_filename': original_filename,
        'total': len(results),
        'positif': int(label_counts.get('Positif', 0)),
        'negatif': int(label_counts.get('Negatif', 0)),
        'netral': int(label_counts.get('Netral', 0)),
        'model': model_choice,
        'waktu': datetime.now().strftime('%d/%m/%Y %H:%M:%S'),
    }
    meta_path = os.path.splitext(filepath)[0] + '.json'
    with open(meta_path, 'w', encoding='utf-8') as f:
        json.dump(meta, f, ensure_ascii=False, indent=2)

    flash(f'Berhasil memproses {len(results)} komentar. Hasil prediksi sudah tersimpan sebagai data global.', 'success')
    return redirect(url_for('admin.prediksi'))


@admin_bp.route('/prediksi/download/<path:filename>')
@login_required
@admin_required
def download_bulk(filename):
    export_dir = _bulk_export_dir()
    filename   = os.path.basename(filename)
    filepath   = os.path.join(export_dir, filename)

    if not os.path.exists(filepath):
        flash('File hasil prediksi tidak ditemukan. Silakan proses ulang dataset.', 'danger')
        return redirect(url_for('admin.prediksi'))

    # Nama file unduhan dibuat dari nama file asli upload agar admin mudah mengenalinya.
    download_name = 'hasil_prediksi_bulk.csv'
    meta_path = os.path.splitext(filepath)[0] + '.json'
    if os.path.exists(meta_path):
        try:
            with open(meta_path, 'r', encoding='utf-8') as f:
                meta = json.load(f)
            original_name = meta.get('original_filename') or meta.get('nama_file_asli')
            if original_name:
                base_name = os.path.splitext(os.path.basename(original_name))[0].strip() or 'bulk'
                download_name = f'hasil_prediksi_{base_name}.csv'
        except Exception:
            pass

    return send_file(filepath, mimetype='text/csv', as_attachment=True, download_name=download_name)


@admin_bp.route('/prediksi/hapus-hasil/<path:filename>', methods=['POST'])
@login_required
@admin_required
def hapus_bulk_result(filename):
    """Hapus file export hasil prediksi massal dari panel admin.
    Catatan: data prediksi global yang sudah masuk ke riwayat/database tidak ikut dihapus.
    """
    export_dir = _bulk_export_dir()
    filename   = os.path.basename(filename)

    if not filename.startswith('hasil_prediksi_bulk_') or not filename.endswith('.csv'):
        flash('Nama file hasil prediksi tidak valid.', 'danger')
        return redirect(url_for('admin.prediksi'))

    csv_path  = os.path.join(export_dir, filename)
    meta_path = os.path.splitext(csv_path)[0] + '.json'

    deleted = False
    for path in (csv_path, meta_path):
        if os.path.exists(path):
            os.remove(path)
            deleted = True

    flash('File hasil prediksi massal berhasil dihapus dari panel.' if deleted else 'File hasil prediksi tidak ditemukan.', 'success' if deleted else 'warning')
    return redirect(url_for('admin.prediksi'))


# ── Riwayat Prediksi ───────────────────────────────────────────────────────
@admin_bp.route('/riwayat')
@login_required
@admin_required
def riwayat():
    page    = request.args.get('page', 1, type=int)
    label   = request.args.get('label', '')
    model_f = request.args.get('model', '')
    user_f  = request.args.get('user_id', '', type=str)
    q       = request.args.get('q', '').strip()

    query = Prediction.query
    if label:
        query = query.filter(Prediction.label_hasil == label)
    if model_f:
        query = query.filter(Prediction.model_dipakai == model_f)
    if user_f:
        query = query.filter(Prediction.user_id == user_f)
    if q:
        query = query.filter(Prediction.komentar.ilike(f'%{q}%'))

    data  = query.order_by(Prediction.created_at.desc()).paginate(page=page, per_page=20)
    users = User.query.filter_by(role='user').all()
    return render_template('admin/riwayat.html', data=data, label=label,
                           model_f=model_f, user_f=user_f, q=q, users=users)


@admin_bp.route('/riwayat/export')
@login_required
@admin_required
def export_riwayat():
    data = Prediction.query.order_by(Prediction.created_at.desc()).all()
    rows = [{
        'id': p.id, 'user': p.user.nama, 'komentar': p.komentar,
        'label': p.label_hasil, 'confidence': p.confidence,
        'model': p.model_dipakai, 'waktu': p.created_at
    } for p in data]
    df  = pd.DataFrame(rows)
    out = io.BytesIO()
    df.to_csv(out, index=False)
    out.seek(0)
    return send_file(out, mimetype='text/csv', as_attachment=True, download_name='riwayat_prediksi.csv')


@admin_bp.route('/riwayat/hapus/<int:id>', methods=['POST'])
@login_required
@admin_required
def hapus_riwayat(id):
    p = Prediction.query.get_or_404(id)
    db.session.delete(p)
    db.session.commit()
    flash('Riwayat berhasil dihapus.', 'success')
    return redirect(url_for('admin.riwayat'))


@admin_bp.route('/riwayat/hapus-semua', methods=['POST'])
@login_required
@admin_required
def hapus_semua_riwayat():
    Prediction.query.delete()
    db.session.commit()
    flash('Semua riwayat berhasil dihapus.', 'success')
    return redirect(url_for('admin.riwayat'))


# ── Word Count ────────────────────────────────────────────────────────────
@admin_bp.route('/wordcount')
@login_required
@admin_required
def wordcount():
    page   = request.args.get('page', 1, type=int)
    search = request.args.get('search', '')
    label  = request.args.get('label', '')

    query = WordCount.query
    if search:
        query = query.filter(WordCount.kata.ilike(f'%{search}%'))
    if label:
        query = query.filter(WordCount.sentimen_dominan == label)

    data = query.order_by(WordCount.frekuensi.desc()).paginate(page=page, per_page=30)
    return render_template('admin/wordcount.html', data=data, search=search, label=label)


@admin_bp.route('/wordcount/rebuild', methods=['POST'])
@login_required
@admin_required
def rebuild_wordcount():
    try:
        rows = extract_wordcount_from_tfidf()
        WordCount.query.delete()
        for r in rows:
            wc = WordCount(
                kata=r['kata'],
                frekuensi=r['frekuensi'],
                sentimen_dominan=r['sentimen_dominan'],
                pct_positif=r['pct_positif'],
                pct_negatif=r['pct_negatif'],
                pct_netral=r['pct_netral'],
            )
            db.session.add(wc)
        db.session.commit()
        flash(f'Word count berhasil diperbarui. {len(rows)} kata ditemukan.', 'success')
    except Exception as e:
        flash(f'Gagal memperbarui word count: {str(e)}', 'danger')
    return redirect(url_for('admin.wordcount'))


# ── Manajemen User ────────────────────────────────────────────────────────
@admin_bp.route('/users')
@login_required
@admin_required
def users():
    page  = request.args.get('page', 1, type=int)
    users = User.query.filter_by(role='user').order_by(User.created_at.desc()).paginate(page=page, per_page=20)
    return render_template('admin/users.html', users=users)


@admin_bp.route('/users/toggle/<int:id>', methods=['POST'])
@login_required
@admin_required
def toggle_user(id):
    user = User.query.get_or_404(id)
    user.status = 'nonaktif' if user.status == 'aktif' else 'aktif'
    db.session.commit()
    flash(f'Status {user.nama} berhasil diubah menjadi {user.status}.', 'success')
    return redirect(url_for('admin.users'))


@admin_bp.route('/users/hapus/<int:id>', methods=['POST'])
@login_required
@admin_required
def hapus_user(id):
    user = User.query.get_or_404(id)
    # Hapus semua prediksi user dulu sebelum hapus user
    Prediction.query.filter_by(user_id=id).delete()
    db.session.delete(user)
    db.session.commit()
    flash(f'User {user.nama} berhasil dihapus.', 'success')
    return redirect(url_for('admin.users'))