import os
import tempfile
import pandas as pd
from flask import Flask, render_template, request, redirect, url_for, flash, send_file
from flask_login import LoginManager, login_user, login_required, logout_user, current_user
from werkzeug.security import generate_password_hash, check_password_hash
from werkzeug.utils import secure_filename
from config import SECRET_KEY, UPLOAD_FOLDER, ALLOWED_EXTENSIONS, STATIC_IMAGES, SQLALCHEMY_DATABASE_URI, BASE_DIR
from models import db, User, AnalysisRun, Dataset, ClassificationMetric, ConfusionMatrix, PreprocessingLog
from utils.labeling import label_score
from utils.preprocessing import preprocess
from utils.features import extract_features
from utils.model import train_and_evaluate
from utils.visualizations import generate_confusion_matrix, generate_wordcloud, generate_distribution

app = Flask(__name__)
app.secret_key = SECRET_KEY
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['SQLALCHEMY_DATABASE_URI'] = SQLALCHEMY_DATABASE_URI
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db.init_app(app)

login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'

os.makedirs(UPLOAD_FOLDER, exist_ok=True)
os.makedirs(STATIC_IMAGES, exist_ok=True)
os.makedirs(os.path.join(BASE_DIR, 'dataset'), exist_ok=True)

with app.app_context():
    db.create_all()


@login_manager.user_loader
def load_user(user_id):
    return db.session.get(User, int(user_id))


def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


@app.route('/')
@login_required
def index():
    runs = AnalysisRun.query.filter_by(user_id=current_user.id)\
        .order_by(AnalysisRun.created_at.desc()).all()
    return render_template('index.html', runs=runs)


@app.route('/scrape', methods=['GET', 'POST'])
@login_required
def scrape():
    from flask import session
    import re
    from scraper import scrape_reviews
    APP_ID_RE = re.compile(r'^[a-zA-Z0-9._]+$')

    if request.method == 'POST':
        app_id = (request.form.get('app_id') or '').strip()
        try:
            count = int(request.form.get('count', 200))
        except ValueError:
            count = 200
        count = max(10, min(count, 3000))

        if not app_id or not APP_ID_RE.match(app_id):
            flash('ID aplikasi tidak valid. Contoh: com.mobile.legends')
            return render_template('scrape.html')

        try:
            df = scrape_reviews(app_id, count=count, lang='id', country='id', sort='newest')
        except Exception as e:
            flash(str(e))
            return render_template('scrape.html', app_id=app_id, count=count)

        # Simpan ke dataset/
        safe_app = re.sub(r'[^a-zA-Z0-9._-]', '_', app_id)
        filename = f"scraped_{safe_app}_{count}.xlsx"
        dataset_dir = os.path.join(BASE_DIR, 'dataset')
        os.makedirs(dataset_dir, exist_ok=True)
        filepath = os.path.join(dataset_dir, filename)
        df.to_excel(filepath, index=False)

        preview = df.head(5).to_dict(orient='records')
        session['scraped_file'] = filename
        return render_template('scrape.html',
                               app_id=app_id, count=count,
                               preview=preview, total=len(df),
                               filename=filename)

    return render_template('scrape.html')


@app.route('/scrape/download/<path:filename>')
@login_required
def scrape_download(filename):
    # Hanya izinkan file hasil scrape di dataset/
    safe = os.path.basename(filename)
    if not safe.startswith('scraped_') or not safe.endswith('.xlsx'):
        flash('File tidak valid.')
        return redirect(url_for('scrape'))
    dataset_dir = os.path.join(BASE_DIR, 'dataset')
    filepath = os.path.join(dataset_dir, safe)
    if not os.path.exists(filepath):
        flash('File tidak ditemukan. Silakan scrape ulang.')
        return redirect(url_for('scrape'))
    return send_file(filepath, as_attachment=True, download_name=safe,
                     mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')


@app.route('/upload')
@login_required
def upload():
    return render_template('upload.html')


@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']

        existing = User.query.filter_by(username=username).first()
        if existing:
            flash('Username sudah terdaftar!')
            return render_template('register.html')

        hashed = generate_password_hash(password)
        user = User(username=username, password=hashed)
        db.session.add(user)
        db.session.commit()
        flash('Registrasi berhasil! Silakan login.')
        return redirect(url_for('login'))

    return render_template('register.html')


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']

        user = User.query.filter_by(username=username).first()

        if user and check_password_hash(user.password, password):
            login_user(user)
            return redirect(url_for('index'))

        flash('Username atau password salah!')

    return render_template('login.html')


@app.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('login'))


@app.route('/analisis', methods=['POST'])
@login_required
def analisis():
    if 'file' not in request.files:
        flash('Pilih file dataset terlebih dahulu! Format .xlsx dengan kolom Sentimen dan Score.')
        return redirect(url_for('upload'))

    file = request.files['file']
    if file.filename == '' or not allowed_file(file.filename):
        flash('Pilih file dataset terlebih dahulu! Format .xlsx dengan kolom Sentimen dan Score.')
        return redirect(url_for('upload'))

    test_size = float(request.form.get('test_size', 0.2))
    batas_positif = int(request.form.get('batas_positif', 4))
    nilai_netral = int(request.form.get('nilai_netral', 3))

    filename = secure_filename(file.filename)
    filepath = os.path.join(UPLOAD_FOLDER, filename)
    file.save(filepath)

    df = pd.read_excel(filepath)

    if 'Sentimen' not in df.columns or 'Score' not in df.columns:
        flash('File Excel harus memiliki kolom "Sentimen" dan "Score"!')
        return redirect(url_for('index'))

    run = AnalysisRun(user_id=current_user.id, filename=filename,
                      test_size=test_size, total_data=len(df))
    db.session.add(run)
    db.session.flush()

    all_logs = []
    texts_by_label = {'Positif': [], 'Netral': [], 'Negatif': []}

    for _, row in df.iterrows():
        sentimen = str(row['Sentimen'])
        try:
            score = int(float(row['Score']))
        except (ValueError, TypeError):
            flash(f'Score tidak valid pada baris: "{sentimen[:50]}"')
            db.session.rollback()
            return redirect(url_for('index'))
        label = label_score(score, batas_positif, nilai_netral)

        result, logs = preprocess(sentimen)

        d = Dataset(run_id=run.id, sentimen_asli=sentimen, score=score,
                    label=label, case_folded=result['case_folded'],
                    cleaned=result['cleaned'], tokenized=result['tokenized'],
                    stopword_removed=result['stopword_removed'],
                    stemmed=result['stemmed'])
        db.session.add(d)

        texts_by_label[label].append(result['stemmed'])
        all_logs.append(logs)

    tahap_keys = ['case_folding', 'cleaning', 'tokenizing',
                  'stopword_removal', 'stemming']
    for tahap in tahap_keys:
        total_sebelum = sum(log[tahap]['sebelum'] for log in all_logs)
        total_sesudah = sum(log[tahap]['sesudah'] for log in all_logs)
        total_waktu = sum(log[tahap]['waktu'] for log in all_logs)
        pl = PreprocessingLog(run_id=run.id, tahap=tahap,
                              jumlah_sebelum=total_sebelum,
                              jumlah_sesudah=total_sesudah,
                              waktu_proses=total_waktu)
        db.session.add(pl)

    data = Dataset.query.with_entities(Dataset.stemmed, Dataset.label)\
        .filter_by(run_id=run.id).all()

    stemmed_texts = []
    y_labels = []
    for row in data:
        if row.stemmed.strip():
            stemmed_texts.append(row.stemmed)
            y_labels.append(row.label)

    unique_labels = set(y_labels)
    if len(unique_labels) < 2:
        db.session.rollback()
        flash('Data harus memiliki minimal 2 label berbeda (Positif/Netral/Negatif)!')
        return redirect(url_for('index'))

    X, vectorizer = extract_features(stemmed_texts)
    accuracy, metrics, cm_data, cm_labels = train_and_evaluate(
        X, y_labels, test_size
    )

    run.accuracy = accuracy

    for m in metrics:
        cm = ClassificationMetric(run_id=run.id, label=m['label'],
                                  precision=m['precision'], recall=m['recall'],
                                  f1_score=m['f1_score'], support=m['support'])
        db.session.add(cm)

    for c in cm_data:
        cmatrix = ConfusionMatrix(run_id=run.id, actual_label=c['actual_label'],
                                  predicted_label=c['predicted_label'],
                                  count=c['count'])
        db.session.add(cmatrix)

    db.session.commit()

    cm_result = generate_confusion_matrix(cm_data, cm_labels, run.id)
    if not cm_result:
        flash('Gagal membuat grafik Confusion Matrix')

    generate_wordcloud(texts_by_label, run.id)

    dist_data = [{'label': k, 'count': len(v)}
                 for k, v in texts_by_label.items() if v]
    generate_distribution(dist_data, run.id)

    return redirect(url_for('hasil', run_id=run.id))


@app.route('/hasil/<int:run_id>')
@login_required
def hasil(run_id):
    run = AnalysisRun.query.filter_by(id=run_id, user_id=current_user.id).first()
    if not run:
        return redirect(url_for('index'))

    metrics = ClassificationMetric.query.filter_by(run_id=run_id).all()
    logs = PreprocessingLog.query.filter_by(run_id=run_id).all()

    from sqlalchemy import func
    dist = db.session.query(Dataset.label, func.count(Dataset.id).label('count'))\
        .filter_by(run_id=run_id).group_by(Dataset.label).all()

    return render_template('hasil.html',
                           run=run, metrics=metrics,
                           logs=logs, dist=dist)


@app.route('/download/<int:run_id>')
@login_required
def download(run_id):
    run = AnalysisRun.query.filter_by(id=run_id, user_id=current_user.id).first()
    if not run:
        return redirect(url_for('index'))

    data = Dataset.query.filter_by(run_id=run_id).all()

    rows = []
    for row in data:
        rows.append({
            'Sentimen Asli': row.sentimen_asli,
            'Score': row.score,
            'Label': row.label,
            'Case Folded': row.case_folded,
            'Cleaned': row.cleaned,
            'Tokenized': row.tokenized,
            'Stopword Removed': row.stopword_removed,
            'Stemmed': row.stemmed
        })

    df = pd.DataFrame(rows)
    tmp = tempfile.NamedTemporaryFile(delete=False, suffix='.xlsx')
    df.to_excel(tmp.name, index=False)

    return send_file(
        tmp.name,
        as_attachment=True,
        download_name=f'preprocessing_{run_id}.xlsx',
        mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
    )


@app.route('/hapus/<int:run_id>', methods=['POST'])
@login_required
def hapus(run_id):
    run = AnalysisRun.query.filter_by(id=run_id, user_id=current_user.id).first()
    if not run:
        flash('Analisis tidak ditemukan!')
        return redirect(url_for('index'))

    for prefix in ['cm', 'dist']:
        path = os.path.join(STATIC_IMAGES, f'{prefix}_{run_id}.png')
        if os.path.exists(path):
            os.remove(path)
    for label in ['Positif', 'Netral', 'Negatif']:
        path = os.path.join(STATIC_IMAGES, f'wc_{run_id}_{label}.png')
        if os.path.exists(path):
            os.remove(path)

    upload_path = os.path.join(UPLOAD_FOLDER, run.filename)
    if os.path.exists(upload_path):
        os.remove(upload_path)

    Dataset.query.filter_by(run_id=run_id).delete()
    ClassificationMetric.query.filter_by(run_id=run_id).delete()
    ConfusionMatrix.query.filter_by(run_id=run_id).delete()
    PreprocessingLog.query.filter_by(run_id=run_id).delete()
    db.session.delete(run)
    db.session.commit()

    flash('Riwayat analisis berhasil dihapus!')
    return redirect(url_for('index'))


if __name__ == '__main__':
    app.run(debug=True)
