import os
import numpy as np
import cv2
import dlib
import pandas as pd
import tensorflow as tf
import ast  # For safely converting strings to lists
from flask import Flask, render_template, Response, request, redirect, url_for, session, flash
import base64
from io import BytesIO
from PIL import Image
import mediapipe as mp
from functools import wraps
from werkzeug.security import generate_password_hash, check_password_hash
import sqlite3

app = Flask(__name__)
app.secret_key = 'your_very_secret_key_here'  # Required for session management
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"  # Disable GPU if needed
print("here")

# Load Pretrained Model and Labels
model = tf.keras.models.load_model("models/model.h5")
print(model)
labels = np.load("models/labels.npy")

# Mediapipe setup
mp_holistic = mp.solutions.holistic
mp_hands = mp.solutions.hands
mp_drawing = mp.solutions.drawing_utils
holistic = mp_holistic.Holistic(min_detection_confidence=0.5, min_tracking_confidence=0.5)

# Load Dlib's Face Detector & Landmark Predictor
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor("models/shape_predictor_68_face_landmarks.dat")

# Load Music Recommendations from CSV
music_df = pd.read_csv("music_recommendations.csv")

# Database initialization
def init_db():
    conn = sqlite3.connect('users.db', timeout=10,  check_same_thread=False)
    cursor = conn.cursor()
    
    # Create users table
    cursor.execute(''' 
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        username TEXT UNIQUE NOT NULL,
        password TEXT NOT NULL,
        email TEXT UNIQUE NOT NULL,
        role TEXT NOT NULL
    )
    ''')
    
    # Create default admin user if not exists
    cursor.execute("SELECT * FROM users WHERE username = 'admin'")
    if cursor.fetchone() is None:
        admin_password = generate_password_hash("admin123")
        cursor.execute("INSERT INTO users (username, password, email, role) VALUES (?, ?, ?, ?)",
                     ("admin", admin_password, "admin@example.com", "admin"))
    
    conn.commit()
    conn.close()

# Call database initialization
init_db()

# Authentication decorators
def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'user_id' not in session:
            flash('Please log in to access this page', 'warning')
            return redirect(url_for('login'))
        return f(*args, **kwargs)
    return decorated_function

def admin_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'user_id' not in session:
            flash('Please log in to access this page', 'warning')
            return redirect(url_for('login'))
        elif session.get('role') != 'admin':
            flash('You need admin privileges to access this page', 'danger')
            return redirect(url_for('home'))
        return f(*args, **kwargs)
    return decorated_function

def get_music_recommendation(emotion):
    """Fetch recommended music based on detected emotion from CSV."""
    # Convert 'seeds' column to lists safely
    music_df["seeds"] = music_df["seeds"].apply(
        lambda x: ast.literal_eval(x) if isinstance(x, str) and x.startswith("[") else x
    )

    # Filter songs where the detected emotion is in the 'seeds' list
    filtered = music_df[music_df["seeds"].apply(lambda seeds: emotion.lower() in [s.lower() for s in seeds])]

    if not filtered.empty:
        return filtered["lastfm_url"].tolist()  # Return a list of matching URLs
    else:
        return ["No recommendation found for this emotion."]
    
def normalize_landmarks(landmarks, width, height):
    return np.array([landmark.x * width, landmark.y * height, landmark.z * width] for landmark in landmarks)

def process_landmarks(landmarks, reference_point=None):
    """Convert landmarks into a relative coordinate vector."""
    lst = []
    for lm in landmarks:
        lst.append(lm[0] - reference_point[0] if reference_point else lm[0])
        lst.append(lm[1] - reference_point[1] if reference_point else lm[1])
    return lst

def predict_emotion(image):
    """Detect face landmarks and predict emotion from image array."""
    if image is None:
        return "Error: Image not found."

    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    faces = detector(gray)

    if len(faces) == 0:
        return "No face detected."

    for face in faces:
        landmarks = predictor(gray, face)
        landmarks = [(p.x, p.y) for p in landmarks.parts()]
        face_landmarks = process_landmarks(landmarks, landmarks[30])  # Reference: Nose tip (index 30)

        feature_vector = np.array(face_landmarks, dtype=np.float32)
        feature_vector = np.pad(feature_vector, (0, max(0, 1020 - len(feature_vector))), mode='constant')[:1020]

        feature_vector = feature_vector.reshape(1, -1)
        pred = model.predict(feature_vector)
        emotion_label = labels[np.argmax(pred)]

        return emotion_label

def predict_emotion_from_file(image_path):
    """Detect face landmarks and predict emotion from file path."""
    image = cv2.imread(image_path)
    return predict_emotion(image)

# Authentication Routes
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        
        conn = sqlite3.connect('users.db')
        cursor = conn.cursor()
        cursor.execute("SELECT id, password, role FROM users WHERE username = ?", (username,))
        user = cursor.fetchone()
        conn.close()
        
        if user and check_password_hash(user[1], password):
            session['user_id'] = user[0]
            session['username'] = username
            session['role'] = user[2]
            
            flash(f'Welcome back, {username}!', 'success')
            if user[2] == 'admin':
                return redirect(url_for('admin_dashboard'))
            else:
                return redirect(url_for('home'))
        else:
            flash('Invalid username or password', 'danger')
    
    return render_template('login.html')

@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        email = request.form['email']
        role = 'user'  # Default role is user
        
        # Check if it's the first user, make them admin
        conn = sqlite3.connect('users.db')
        cursor = conn.cursor()
        cursor.execute("SELECT COUNT(*) FROM users")
        if cursor.fetchone()[0] == 0:
            role = 'admin'
        
        # Check if username or email already exists
        cursor.execute("SELECT id FROM users WHERE username = ? OR email = ?", (username, email))
        if cursor.fetchone():
            conn.close()
            flash('Username or email already exists', 'danger')
            return redirect(url_for('register'))
        
        # Hash password and save user
        hashed_password = generate_password_hash(password)
        cursor.execute("INSERT INTO users (username, password, email, role) VALUES (?, ?, ?, ?)",
                     (username, hashed_password, email, role))
        conn.commit()
        conn.close()
        
        flash('Registration successful! You can now log in.', 'success')
        return redirect(url_for('login'))
    
    return render_template('register.html')

@app.route('/logout')
def logout():
    session.clear()
    flash('You have been logged out', 'info')
    return redirect(url_for('login'))

# Admin Routes
@app.route('/admin')
@admin_required
def admin_dashboard():
    conn = sqlite3.connect('users.db')
    cursor = conn.cursor()
    cursor.execute("SELECT id, username, email, role FROM users")
    users = cursor.fetchall()
    conn.close()
    
    return render_template('admin_dashboard.html', users=users)

@app.route('/admin/delete_user/<int:user_id>', methods=['POST'])
@admin_required
def delete_user(user_id):
    # Don't allow admins to delete themselves
    if user_id == session.get('user_id'):
        flash('You cannot delete your own account while logged in', 'danger')
        return redirect(url_for('admin_dashboard'))
    
    conn = sqlite3.connect('users.db')
    cursor = conn.cursor()
    cursor.execute("DELETE FROM users WHERE id = ?", (user_id,))
    conn.commit()
    conn.close()
    
    flash('User has been deleted', 'success')
    return redirect(url_for('admin_dashboard'))

@app.route('/admin/edit_user/<int:user_id>', methods=['GET', 'POST'])
@admin_required
def edit_user(user_id):
    try:
        with sqlite3.connect('users.db', timeout=10, check_same_thread=False) as conn:
            cursor = conn.cursor()

            if request.method == 'POST':
                username = request.form['username']
                email = request.form['email']
                role = request.form['role']

                cursor.execute(""" 
                    UPDATE users SET username = ?, email = ?, role = ? WHERE id = ? 
                """, (username, email, role, user_id))

                conn.commit()
                flash('User updated successfully!', 'success')
                return redirect(url_for('admin_dashboard'))

            # GET request
            cursor.execute("SELECT id, username, email, role FROM users WHERE id = ?", (user_id,))
            user = cursor.fetchone()

            if not user:
                flash('User not found.', 'danger')
                return redirect(url_for('admin_dashboard'))

            return render_template('edit_user.html', user=user)

    except sqlite3.OperationalError as e:
        flash('Database error: ' + str(e), 'danger')
        return redirect(url_for('admin_dashboard'))

# Main Application Routes
@app.route('/')
def index():
    # Redirect to login if not authenticated
    if 'user_id' not in session:
        return redirect(url_for('login'))
    return redirect(url_for('home'))

@app.route('/home')
@login_required
def home():
    return render_template('index.html', username=session.get('username'))

@app.route('/recommend_music', methods=['POST'])
@login_required
def handle_music_request():
    if 'file' not in request.files:
        return render_template('index.html', error="No file part")
    file = request.files['file']
    if file.filename == '':
        return render_template('index.html', error="No selected file")
    
    # Convert the uploaded file to an image
    image = Image.open(file.stream)
    image = np.array(image)
    
    # Predict emotion
    emotion = predict_emotion(image)
    
    # Get music recommendations
    recommendations = get_music_recommendation(emotion)
    
    return render_template('music_recommendations.html', emotion=emotion, recommendations=recommendations)

if __name__ == '__main__':
    app.run(debug=True, port=5001)




Traceback (most recent call last):
  File "C:\Users\mani3\miniconda3\envs\music_rec311\Lib\site-packages\flask\app.py", line 1536, in __call__
    return self.wsgi_app(environ, start_response)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mani3\miniconda3\envs\music_rec311\Lib\site-packages\flask\app.py", line 1514, in wsgi_app
    response = self.handle_exception(e)
               ^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mani3\miniconda3\envs\music_rec311\Lib\site-packages\flask\app.py", line 1511, in wsgi_app
    response = self.full_dispatch_request()
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mani3\miniconda3\envs\music_rec311\Lib\site-packages\flask\app.py", line 919, in full_dispatch_request
    rv = self.handle_user_exception(e)
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mani3\miniconda3\envs\music_rec311\Lib\site-packages\flask\app.py", line 917, in full_dispatch_request
    rv = self.dispatch_request()
         ^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mani3\miniconda3\envs\music_rec311\Lib\site-packages\flask\app.py", line 902, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mani3\Downloads\MoodSick-main\MoodSick-main\manu\app_new.py", line 76, in decorated_function
    return f(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^
  File "C:\Users\mani3\Downloads\MoodSick-main\MoodSick-main\manu\app_new.py", line 289, in home
    return render_template('index.html', username=session.get('username'))
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mani3\miniconda3\envs\music_rec311\Lib\site-packages\flask\templating.py", line 150, in render_template
    return _render(app, template, context)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mani3\miniconda3\envs\music_rec311\Lib\site-packages\flask\templating.py", line 131, in _render
    rv = template.render(context)
         ^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mani3\miniconda3\envs\music_rec311\Lib\site-packages\jinja2\environment.py", line 1295, in render
    self.environment.handle_exception()
  File "C:\Users\mani3\miniconda3\envs\music_rec311\Lib\site-packages\jinja2\environment.py", line 942, in handle_exception
    raise rewrite_traceback_stack(source=source)
  File "C:\Users\mani3\Downloads\MoodSick-main\MoodSick-main\manu\templates\index.html", line 563, in top-level template code
    <a class="nav-link" href="{{ url_for('webcam_page') }}">Webcam</a>
  File "C:\Users\mani3\miniconda3\envs\music_rec311\Lib\site-packages\flask\app.py", line 1121, in url_for
    return self.handle_url_build_error(error, endpoint, values)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mani3\miniconda3\envs\music_rec311\Lib\site-packages\flask\app.py", line 1110, in url_for
    rv = url_adapter.build(  # type: ignore[union-attr]
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\mani3\miniconda3\envs\music_rec311\Lib\site-packages\werkzeug\routing\map.py", line 924, in build
    raise BuildError(endpoint, values, method, self)
werkzeug.routing.exceptions.BuildError: Could not build url for endpoint 'webcam_page'. Did you mean 'register' instead?
INFO:werkzeug:127.0.0.1 - - [20/Apr/2025 13:15:47] "GET /home?__debugger__=yes&cmd=resource&f=style.css HTTP/1.1" 200 -
INFO:werkzeug:127.0.0.1 - - [20/Apr/2025 13:15:47] "GET /home?__debugger__=yes&cmd=resource&f=debugger.js HTTP/1.1" 200 -
INFO:werkzeug:127.0.0.1 - - [20/Apr/2025 13:15:47] "GET /home?__debugger__=yes&cmd=resource&f=console.png&s=7VyRxDhL399GTHElhlZp HTTP/1.1" 200 -
INFO:werkzeug:127.0.0.1 - - [20/Apr/2025 13:15:47] "GET /home?__debugger__=yes&cmd=resource&f=console.png HTTP/1.1" 200 -