import os
import uuid
import subprocess
import re
import numpy as np
import mysql.connector
from flask import Flask, request, jsonify, render_template, redirect, url_for
from flask_cors import CORS
from resemblyzer import VoiceEncoder, preprocess_wav
from numpy.linalg import norm
import whisper
from werkzeug.utils import secure_filename

# ----- CONFIG -----
DB_CONFIG = {
    "host": "localhost",
    "user": "root",
    "password": "",
    "database": "voice_auth",
}

UPLOAD_FOLDER = "uploads"
os.makedirs(UPLOAD_FOLDER, exist_ok=True)

SIMILARITY_THRESHOLD = 0.75

app = Flask(__name__, static_folder="static", template_folder="templates")
CORS(app)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

encoder = VoiceEncoder()
model_whisper = whisper.load_model("base")

def get_db_connection():
    return mysql.connector.connect(**DB_CONFIG)

def ensure_tables():
    conn = get_db_connection()
    cur = conn.cursor()
    # Table users (pour authentification vocale)
    cur.execute("""
    CREATE TABLE IF NOT EXISTS users (
        id VARCHAR(36) PRIMARY KEY,
        created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
        first_name VARCHAR(100) DEFAULT NULL,
        last_name VARCHAR(100) DEFAULT NULL,
        voice_embedding LONGBLOB NOT NULL
    )
    """)
    # Table incidents
    cur.execute("""
    CREATE TABLE IF NOT EXISTS incidents (
        id INT AUTO_INCREMENT PRIMARY KEY,
        user_id VARCHAR(36) NULL,
        type VARCHAR(100) NOT NULL,
        photo_path VARCHAR(255) NOT NULL,
        audio_path VARCHAR(255) NOT NULL,
        latitude VARCHAR(20) NOT NULL,
        longitude VARCHAR(20) NOT NULL,
        date_incident DATETIME NOT NULL
    )
    """)
    conn.commit()
    cur.close()
    conn.close()

ensure_tables()

def convert_to_wav_pcm(input_path, output_path):
    cmd = [
        "ffmpeg", "-y", "-i", input_path,
        "-ar", "16000", "-ac", "1", "-c:a", "pcm_s16le",
        output_path
    ]
    subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)

def cosine_similarity(a, b):
    return float(np.dot(a, b) / (norm(a) * norm(b)))

def extract_names_from_text(text):
    match = re.search(r"je m'appelle ([a-zA-Z]+) ([a-zA-Z]+)", text, re.I)
    if match:
        return match.group(1).capitalize(), match.group(2).capitalize()
    return None, None

def compute_embedding(audio_path):
    wav = preprocess_wav(audio_path)
    emb = encoder.embed_utterance(wav)
    return emb

@app.route("/")
def index():
    return render_template("index.html")

@app.route("/enroll", methods=["POST"])
def enroll():
    if "audio" not in request.files:
        return jsonify({"success": False, "message": "Aucun fichier audio reçu"}), 400
    file = request.files["audio"]

    tmp_name = str(uuid.uuid4())
    raw_path = os.path.join(UPLOAD_FOLDER, tmp_name + ".webm")
    wav_path = os.path.join(UPLOAD_FOLDER, tmp_name + "_conv.wav")

    file.save(raw_path)

    try:
        convert_to_wav_pcm(raw_path, wav_path)
        result = model_whisper.transcribe(wav_path, language="fr")
        texte = result["text"].strip()

        first_name, last_name = extract_names_from_text(texte)
        if not first_name or not last_name:
            return jsonify({
                "success": False,
                "message": "Nom et prénom non détectés dans la voix. Merci de dire clairement 'Je m'appelle Prénom Nom'."
            }), 400

        emb = compute_embedding(wav_path)
        emb_bytes = emb.tobytes()

        user_id = str(uuid.uuid4())

        conn = get_db_connection()
        cur = conn.cursor()
        cur.execute(
            "INSERT INTO users (id, first_name, last_name, voice_embedding) VALUES (%s, %s, %s, %s)",
            (user_id, first_name, last_name, emb_bytes)
        )
        conn.commit()
        cur.close()
        conn.close()

        return jsonify({
            "success": True,
            "message": f"Enrôlement réussi pour {first_name} {last_name}",
            "user_id": user_id
        })

    except subprocess.CalledProcessError as e:
        return jsonify({"success": False, "message": "Erreur ffmpeg : " + str(e)}), 500
    except Exception as e:
        return jsonify({"success": False, "message": "Erreur interne : " + str(e)}), 500
    finally:
        for p in (raw_path, wav_path):
            try:
                if os.path.exists(p):
                    os.remove(p)
            except:
                pass

@app.route("/auth", methods=["POST"])
def auth():
    if "audio" not in request.files:
        return jsonify({"success": False, "message": "Aucun fichier audio reçu"}), 400

    file = request.files["audio"]
    tmp_name = str(uuid.uuid4())
    raw_path = os.path.join(UPLOAD_FOLDER, tmp_name + ".webm")
    wav_path = os.path.join(UPLOAD_FOLDER, tmp_name + "_conv.wav")
    file.save(raw_path)

    try:
        convert_to_wav_pcm(raw_path, wav_path)
        emb = compute_embedding(wav_path)

        conn = get_db_connection()
        cur = conn.cursor()
        cur.execute("SELECT id, voice_embedding, first_name, last_name FROM users")
        rows = cur.fetchall()
        cur.close()
        conn.close()

        best = {"id": None, "score": -1, "first_name": None, "last_name": None}
        for row in rows:
            user_id = row[0]
            emb_bytes = row[1]
            stored = np.frombuffer(emb_bytes, dtype=np.float32)
            score = cosine_similarity(emb, stored)
            if score > best["score"]:
                best = {
                    "id": user_id,
                    "score": score,
                    "first_name": row[2],
                    "last_name": row[3]
                }

        if best["score"] >= SIMILARITY_THRESHOLD:
            return jsonify({
                "success": True,
                "user_id": best["id"],
                "score": best["score"],
                "first_name": best["first_name"],
                "last_name": best["last_name"],
                "message": "Authentification réussie."
            })
        else:
            return jsonify({
                "success": False,
                "score": best["score"],
                "message": "Voix non reconnue."
            }), 401

    except subprocess.CalledProcessError as e:
        return jsonify({"success": False, "message": "Erreur ffmpeg : " + str(e)}), 500
    except Exception as e:
        return jsonify({"success": False, "message": "Erreur interne : " + str(e)}), 500
    finally:
        for p in (raw_path, wav_path):
            try:
                if os.path.exists(p):
                    os.remove(p)
            except:
                pass

@app.route("/menu")
def menu():
    return render_template("menu.html")

@app.route('/photo_capture')
def photo_capture():
    incident_type = request.args.get('type', 'inconnu')
    return render_template('photo_capture.html', incident_type=incident_type)

@app.route('/save_incident', methods=['POST'])
def save_incident():
    incident_type = request.form.get('type', 'inconnu')
    lat = request.form.get('lat')
    lng = request.form.get('lng')

    audio_file = request.files.get('audio_file')
    photo_file = request.files.get('photo')

    if not audio_file or not photo_file:
        return "Audio ou photo manquants", 400

    audio_filename = secure_filename(f"{uuid.uuid4()}_{audio_file.filename}")
    photo_filename = secure_filename(f"{uuid.uuid4()}_{photo_file.filename}")

    audio_path = os.path.join(UPLOAD_FOLDER, audio_filename)
    photo_path = os.path.join(UPLOAD_FOLDER, photo_filename)

    audio_file.save(audio_path)
    photo_file.save(photo_path)

    # Enregistrer en base de données
    try:
        conn = get_db_connection()
        cur = conn.cursor()
        cur.execute("""
            INSERT INTO incidents (user_id, type, photo_path, audio_path, latitude, longitude, date_incident)
            VALUES (%s, %s, %s, %s, %s, %s, NOW())
        """, (
            None,  # TODO: Remplacer par user_id si gestion session implémentée
            incident_type,
            photo_filename,
            audio_filename,
            lat,
            lng
        ))
        conn.commit()
        cur.close()
        conn.close()
    except Exception as e:
        return f"Erreur base de données : {e}", 500

    return f"Incident '{incident_type}' enregistré avec succès ! Position: {lat}, {lng}"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)
