"""
MHA Flow v2.0.8 - Production Flask Application
Complete system with authentication, visualization, and advanced features
"""
import os
import json
import time
import numpy as np
from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, session
from flask_login import LoginManager, login_user, logout_user, login_required, current_user
from werkzeug.utils import secure_filename
from functools import wraps
from datetime import datetime, timedelta

# Load environment variables from .env file
from dotenv import load_dotenv
load_dotenv()

# Import configuration
from config import get_config

# Import SQL models
from models import db, User, OptimizationResult, CustomAlgorithm, ComparisonSession, SystemSettings

# Import database helpers
from db_helpers import (
    get_user_by_id, get_user_by_username, get_user_by_email, count_users,
    save_object, delete_object, rollback, get_optimization_result_by_id,
    get_user_optimization_results, get_user_custom_algorithms,
    count_user_optimizations, count_user_custom_algos, count_user_comparisons,
    get_best_result, count_successful_runs, get_all_completed_results, get_user_id_for_mongodb
)

# Import MHA components
from mha_toolbox.complete_algorithm_registry_v2 import AlgorithmRegistry
from mha_toolbox.custom_algorithm_manager import CustomAlgorithmManager
from mha_toolbox.algorithm_recommender import AlgorithmRecommender
from mha_toolbox.hyperparameter_tuning import HyperparameterTuner

# Import objective functions
from objective_functions.standard_functions import OBJECTIVE_FUNCTIONS

def load_algorithm_class(algorithm_name):
    """Dynamically load algorithm class from file"""
    try:
        # Get algorithm info from registry
        algo_info = registry.get_algorithm(algorithm_name)
        if not algo_info:
            return None
        
        # Get the file name (e.g., 'pso.py' -> 'pso')
        file_name = algo_info.get('file', '').replace('.py', '')
        if not file_name:
            return None
        
        # Determine module path from file name
        # Registry may store paths like 'hybrid/abc_de_hybrid' — normalize to module path
        if '/' in file_name:
            # Convert path separators to module dots: 'hybrid/abc_de_hybrid' -> 'hybrid.abc_de_hybrid'
            module_path = f'mha_toolbox.algorithms.{file_name.replace("/", ".")}'
        else:
            # Try direct path first, fall back to hybrid subfolder
            module_path = f'mha_toolbox.algorithms.{file_name}'
        
        # Dynamic import
        import importlib
        import inspect
        
        try:
            module = importlib.import_module(module_path)
        except ModuleNotFoundError:
            # If direct import fails and it's a hybrid, try hybrid subfolder
            category = algo_info.get('category', '')
            if category == 'Hybrid' or 'hybrid' in file_name.lower():
                module_path = f'mha_toolbox.algorithms.hybrid.{file_name}'
                module = importlib.import_module(module_path)
            else:
                raise
        
        # Get BaseOptimizer class for comparison
        from mha_toolbox.base import BaseOptimizer
        
        # Find classes that inherit from BaseOptimizer (but not BaseOptimizer itself)
        algorithm_classes = []
        for name, obj in inspect.getmembers(module, inspect.isclass):
            if obj != BaseOptimizer and issubclass(obj, BaseOptimizer):
                algorithm_classes.append(obj)
        
        # Return the first valid algorithm class found
        if algorithm_classes:
            return algorithm_classes[0]
        
        return None
        
    except Exception as e:
        return None


def run_algorithm(algorithm_class, objective_func, population_size, max_iterations,
                  dimensions, lower_bound, upper_bound):
    """
    Instantiate and run an algorithm, handling both new-style and legacy patterns.
    
    Returns (result_model_or_tuple, execution_time) where result_model has
    .best_solution_, .best_fitness_, .global_fitness_ attributes.
    """
    import time as _time
    
    # Try standard BaseOptimizer pattern first
    try:
        algorithm = algorithm_class(
            population_size=population_size,
            max_iterations=max_iterations
        )
        algorithm.dimensions_ = dimensions
        algorithm.lower_bound_ = lower_bound
        algorithm.upper_bound_ = upper_bound
        
        start_time = _time.time()
        result_model = algorithm.optimize(objective_function=objective_func)
        execution_time = _time.time() - start_time
        return result_model, execution_time
    except TypeError:
        pass
    
    # Fallback: legacy algorithms that need objective_function/dimensions/bounds in __init__
    try:
        bounds = np.array([[lower_bound, upper_bound]] * dimensions)
        algorithm = algorithm_class(
            objective_function=objective_func,
            dimensions=dimensions,
            bounds=bounds,
            population_size=population_size,
            max_iterations=max_iterations
        )
        start_time = _time.time()
        result = algorithm.optimize()  # Legacy optimize() takes no args
        execution_time = _time.time() - start_time
        
        # Wrap 3-tuple return into a model-like object
        if isinstance(result, tuple):
            from types import SimpleNamespace
            best_sol, best_fit, conv = result[0], result[1], result[2] if len(result) > 2 else []
            result_model = SimpleNamespace(
                best_solution_=np.array(best_sol),
                best_fitness_=float(best_fit),
                global_fitness_=conv if conv else [],
                execution_time_=execution_time
            )
            return result_model, execution_time
        return result, execution_time
    except Exception:
        raise

# Get configuration based on environment
env = os.environ.get('FLASK_ENV', 'development')
config_obj = get_config(env)

app = Flask(__name__)
app.config.from_object(config_obj)

# SQLite database initialization
app.config['SQLALCHEMY_DATABASE_URI'] = config_obj.get_database_uri()
print("✅ Using SQLite Database")

# Initialize SQL database
db.init_app(app)

# Initialize Flask-Login
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
login_manager.login_message = 'Please log in to access this page.'

# Initialize MHA components
registry = AlgorithmRegistry()
custom_manager = CustomAlgorithmManager()
recommender = AlgorithmRecommender()

# Ensure upload folder exists
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)


@login_manager.user_loader
def load_user(user_id):
    """Load user by ID"""
    return get_user_by_id(user_id)


def admin_required(f):
    """Decorator for admin-only routes"""
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not current_user.is_authenticated or not current_user.is_admin:
            flash('Admin access required', 'error')
            return redirect(url_for('index'))
        return f(*args, **kwargs)
    return decorated_function


# ============================================================================
# STATIC ROUTES
# ============================================================================

@app.route('/favicon.ico')
def favicon():
    """Serve favicon to prevent 404 errors"""
    return '', 204  # No Content response

# ============================================================================
# AUTHENTICATION ROUTES
# ============================================================================

@app.route('/register', methods=['GET', 'POST'])
def register():
    """User registration"""
    if current_user.is_authenticated:
        return redirect(url_for('dashboard'))
    
    if request.method == 'POST':
        data = request.form if request.form else request.json
        username = data.get('username', '').strip()
        email = data.get('email', '').strip()
        password = data.get('password', '')
        
        # Validation
        if not username or not email or not password:
            return jsonify({'error': 'All fields are required'}), 400
        
        if len(password) < 6:
            return jsonify({'error': 'Password must be at least 6 characters'}), 400
        
        # Check if user exists
        if get_user_by_username(username):
            return jsonify({'error': 'Username already exists'}), 400
        
        if get_user_by_email(email):
            return jsonify({'error': 'Email already registered'}), 400
        
        # Create user
        user = User(username=username, email=email)
        user.set_password(password)
        
        # First user becomes admin
        if count_users() == 0:
            user.is_admin = True
        
        save_object(user)
        
        login_user(user)
        flash('Registration successful!', 'success')
        
        if request.is_json:
            return jsonify({'message': 'Registration successful', 'redirect': url_for('dashboard')}), 201
        return redirect(url_for('dashboard'))
    
    return render_template('register.html')


@app.route('/login', methods=['GET', 'POST'])
def login():
    """User login"""
    if current_user.is_authenticated:
        return redirect(url_for('dashboard'))
    
    if request.method == 'POST':
        username = request.form.get('username', '').strip()
        password = request.form.get('password', '')
        remember = request.form.get('remember', type=bool)
        
        user = get_user_by_username(username)
        
        if user and user.check_password(password):
            login_user(user, remember=remember)
            user.update_last_login()
            
            flash(f'Welcome back, {user.username}!', 'success')
            
            next_page = request.args.get('next')
            return redirect(next_page or url_for('dashboard'))
        
        flash('Invalid username or password', 'error')
    
    return render_template('login.html')


@app.route('/api/auth/login', methods=['POST'])
def api_login():
    """API endpoint for login (matches frontend expectations)"""
    try:
        data = request.get_json()
        username = data.get('username', '').strip()
        password = data.get('password', '')
        
        if not username or not password:
            return jsonify({'error': 'Username and password are required'}), 400
        
        user = get_user_by_username(username)
        
        if user and user.check_password(password):
            # Make session permanent before login
            session.permanent = True
            login_user(user, remember=True)
            user.update_last_login()
            
            # Debug: Check session
            
            # Return redirect URL and set delay for cookie
            return jsonify({
                'success': True,
                'message': 'Login successful',
                'username': username,
                'redirect': '/dashboard'
            }), 200
        
        return jsonify({'error': 'Invalid username or password'}), 401
        
    except Exception as e:
        return jsonify({'error': str(e)}), 500


@app.route('/api/auth/register', methods=['POST'])
def api_register():
    """API endpoint for registration"""
    try:
        data = request.get_json()
        username = data.get('username', '').strip()
        email = data.get('email', '').strip()
        password = data.get('password', '')
        
        # Validation
        if not username or not email or not password:
            return jsonify({'error': 'All fields are required'}), 400
        
        if len(username) < 3:
            return jsonify({'error': 'Username must be at least 3 characters'}), 400
        
        if len(password) < 6:
            return jsonify({'error': 'Password must be at least 6 characters'}), 400
        
        # Check if user exists
        if get_user_by_username(username):
            return jsonify({'error': 'Username already exists'}), 409
        
        if get_user_by_email(email):
            return jsonify({'error': 'Email already registered'}), 409
        
        # Create user
        user = User(username=username, email=email, is_active=True)
        user.set_password(password)
        
        save_object(user)
        
        login_user(user)
        
        return jsonify({
            'message': 'Registration successful',
            'user': {
                'username': user.username,
                'email': user.email
            }
        }), 201
        
    except Exception as e:
        rollback()
        return jsonify({'error': 'Registration failed. Please try again.'}), 500


@app.route('/logout')
@login_required
def logout():
    """User logout"""
    logout_user()
    flash('You have been logged out', 'info')
    return redirect(url_for('index'))


# ============================================================================
# MAIN ROUTES
# ============================================================================

@app.route('/')
def index():
    """Landing page"""
    if current_user.is_authenticated:
        return redirect(url_for('dashboard'))
    
    return render_template('landing.html')


@app.route('/dashboard')
@login_required
def dashboard():
    """User dashboard"""
    
    # Get user ID (integer)
    user_id = current_user.id
    
    # Get user's recent results
    recent_results = get_user_optimization_results(user_id, limit=5)
    
    # Get user's custom algorithms
    custom_algos = get_user_custom_algorithms(user_id, limit=5)
    
    # Statistics
    total_optimizations = count_user_optimizations(user_id)
    total_custom_algos = count_user_custom_algos(user_id)
    
    # Best result
    best_result = get_best_result(user_id)
    
    # Calculate success rate
    successful_runs = count_successful_runs(user_id)
    
    # Calculate average improvement (if results exist)
    avg_improvement = 0
    if total_optimizations > 0:
        results = get_all_completed_results(user_id)
        if results:
            improvements = []
            for r in results:
                if r.best_fitness and r.best_fitness < 1000:  # Filter outliers
                    improvement = abs((1.0 - r.best_fitness) * 100)
                    improvements.append(min(improvement, 100))
            if improvements:
                avg_improvement = sum(improvements) / len(improvements)
    
    # Algorithm category counts
    try:
        swarm_algos = registry.get_algorithms_by_category('swarm_intelligence')
        evolutionary_algos = registry.get_algorithms_by_category('evolutionary')
        physics_algos = registry.get_algorithms_by_category('physics_based')
        hybrid_algos = registry.get_algorithms_by_category('hybrid')
        
        swarm_count = len(swarm_algos) if swarm_algos else 0
        evolutionary_count = len(evolutionary_algos) if evolutionary_algos else 0
        physics_count = len(physics_algos) if physics_algos else 0
        hybrid_count = len(hybrid_algos) if hybrid_algos else 0
    except:
        # Fallback counts
        swarm_count = 15
        evolutionary_count = 8
        physics_count = 5
        hybrid_count = 3
    
    stats = {
        'total_algorithms': registry.get_total_count(),
        'user_experiments': total_optimizations,
        'successful_runs': successful_runs,
        'avg_improvement': int(avg_improvement),
        'swarm_count': swarm_count,
        'evolutionary_count': evolutionary_count,
        'physics_count': physics_count,
        'hybrid_count': hybrid_count
    }
    
    return render_template('dashboard.html',
                         recent_results=recent_results,
                         custom_algos=custom_algos,
                         total_optimizations=total_optimizations,
                         total_custom_algos=total_custom_algos,
                         best_result=best_result,
                         stats=stats)


@app.route('/profile')
@login_required
def profile():
    """User profile page"""
    user_id = get_user_id_for_mongodb(current_user.id)
    
    # Get stats
    stats = {
        'total_optimizations': count_user_optimizations(user_id),
        'custom_algorithms': count_user_custom_algos(user_id),
        'total_comparisons': count_user_comparisons(user_id)
    }
    
    # Get recent optimization results (last 5)
    recent_results = get_user_optimization_results(user_id, limit=5)
    
    # Get algorithm submissions
    submissions = get_user_custom_algorithms(user_id)
    
    return render_template('profile.html',
                         stats=stats,
                         recent_results=recent_results,
                         submissions=submissions)


@app.route('/profile/update', methods=['POST'])
@login_required
def update_profile():
    """Update user profile"""
    email = request.form.get('email')
    new_password = request.form.get('new_password')
    confirm_password = request.form.get('confirm_password')
    
    try:
        if email and email != current_user.email:
            # Check if email already exists
            existing = get_user_by_email(email)
            if existing and existing.id != current_user.id:
                flash('Email already in use', 'error')
                return redirect(url_for('profile'))
            current_user.email = email
        
        if new_password:
            if new_password != confirm_password:
                flash('Passwords do not match', 'error')
                return redirect(url_for('profile'))
            if len(new_password) < 6:
                flash('Password must be at least 6 characters', 'error')
                return redirect(url_for('profile'))
            current_user.set_password(new_password)
        
        save_object(current_user)
        flash('Profile updated successfully!', 'success')
        
    except Exception as e:
        flash(f'Error updating profile: {str(e)}', 'error')
    
    return redirect(url_for('profile'))


@app.route('/optimize')
@login_required
def optimize_page():
    """Optimization interface"""
    # Get algorithm names only (not full dict objects)
    all_algos = registry.list_algorithms()
    algorithm_names = [algo['code'] if isinstance(algo, dict) else str(algo) for algo in all_algos]
    objective_funcs = list(OBJECTIVE_FUNCTIONS.keys())
    
    return render_template('optimize.html',
                         algorithms=algorithm_names,
                         objective_functions=objective_funcs)


@app.route('/compare')
@login_required
def compare_page():
    """Algorithm comparison interface"""
    # Get algorithm names only
    all_algos = registry.list_algorithms()
    algorithm_names = [algo['code'] if isinstance(algo, dict) else str(algo) for algo in all_algos]
    objective_funcs = list(OBJECTIVE_FUNCTIONS.keys())
    
    return render_template('compare.html',
                         algorithms=algorithm_names,
                         objective_functions=objective_funcs)


@app.route('/upload-algorithm')
@login_required
def upload_algorithm_page():
    """Custom algorithm upload interface"""
    return render_template('upload_algorithm.html')


@app.route('/dataset-upload')
@login_required
def dataset_upload_page():
    """Dataset upload interface"""
    return render_template('dataset_upload.html')


@app.route('/algorithm-recommend')
@login_required
def algorithm_recommend_page():
    """Algorithm recommendation interface"""
    return render_template('algorithm_recommend.html')


@app.route('/levify')
@login_required
def levify_page():
    """Levy Flight transformation interface"""
    return render_template('levify.html')


@app.route('/settings')
@login_required
def settings_page():
    """User settings"""
    return render_template('settings.html')


@app.route('/results')
@login_required
def results():
    """View comprehensive optimization results"""
    return render_template('results.html')


@app.route('/results/<int:result_id>')
@login_required
def view_result(result_id):
    """View detailed optimization result"""
    result = OptimizationResult.query.get_or_404(result_id)
    
    # Check if user owns this result
    if result.user_id != current_user.id and not current_user.is_admin:
        flash('Access denied', 'error')
        return redirect(url_for('dashboard'))
    
    return render_template('result_detail.html', result=result)


@app.route('/history')
@login_required
def history():
    """View optimization history"""
    page = request.args.get('page', 1, type=int)
    per_page = 20
    
    results = OptimizationResult.query.filter_by(user_id=current_user.id)\
        .order_by(OptimizationResult.created_at.desc())\
        .paginate(page=page, per_page=per_page, error_out=False)
    
    return render_template('history.html', results=results)


@app.route('/api/history', methods=['GET'])
@login_required
def api_get_history():
    """Get optimization history for current user"""
    results = OptimizationResult.query.filter_by(user_id=current_user.id)\
        .order_by(OptimizationResult.created_at.desc())\
        .all()
    
    history_data = []
    for result in results:
        history_data.append({
            'id': result.id,
            'algorithm': result.algorithm_name,
            'objective_function': result.objective_function,
            'best_fitness': result.best_fitness,
            'execution_time': result.execution_time,
            'population_size': result.population_size,
            'max_iterations': result.max_iterations,
            'dimensions': result.dimensions,
            'lower_bound': result.lower_bound,
            'upper_bound': result.upper_bound,
            'status': result.status,
            'created_at': result.created_at.isoformat() if result.created_at else None,
            'convergence_curve': result.get_convergence_data(),
            'best_solution': result.get_best_position()
        })
    
    return jsonify({'history': history_data}), 200


@app.route('/api/history/<int:result_id>', methods=['DELETE'])
@login_required
def api_delete_result(result_id):
    """Delete an optimization result"""
    result = get_optimization_result_by_id(result_id)
    if not result:
        return jsonify({'error': 'Result not found'}), 404
    
    # Check if user owns this result
    user_id = current_user.id
    if result.user_id != user_id and not current_user.is_admin:
        return jsonify({'error': 'Access denied'}), 403
    
    delete_object(result)
    
    return jsonify({'message': 'Result deleted successfully'}), 200


@app.route('/api/history/<int:result_id>/rerun', methods=['POST'])
@login_required
def api_rerun_result(result_id):
    """Rerun an optimization with same parameters"""
    original_result = OptimizationResult.query.get_or_404(result_id)
    
    # Check if user owns this result
    if original_result.user_id != current_user.id and not current_user.is_admin:
        return jsonify({'error': 'Access denied'}), 403
    
    # Return the parameters to re-run
    return jsonify({
        'algorithm': original_result.algorithm_name,
        'objective_function': original_result.objective_function,
        'population_size': original_result.population_size,
        'max_iterations': original_result.max_iterations,
        'dimensions': original_result.dimensions,
        'lower_bound': original_result.lower_bound,
        'upper_bound': original_result.upper_bound
    }), 200


# ============================================================================
# API ROUTES
# ============================================================================

@app.route('/api/generate-dataset', methods=['POST'])
@login_required
def api_generate_dataset():
    """Generate random dataset for testing"""
    data = request.json
    
    try:
        from sklearn.datasets import make_classification, make_regression
        import pandas as pd
        import io
        
        n_samples = int(data.get('n_samples', 1000))
        n_features = int(data.get('n_features', 10))
        problem_type = data.get('problem_type', 'classification')
        n_classes = int(data.get('n_classes', 2))
        noise = float(data.get('noise', 0.1))
        
        # Validate inputs
        if n_samples < 10 or n_samples > 100000:
            return jsonify({'error': 'Samples must be between 10 and 100,000'}), 400
        if n_features < 2 or n_features > 1000:
            return jsonify({'error': 'Features must be between 2 and 1,000'}), 400
        
        # Generate dataset
        if problem_type == 'classification':
            X, y = make_classification(
                n_samples=n_samples,
                n_features=n_features,
                n_classes=n_classes,
                n_informative=max(2, n_features // 2),
                n_redundant=min(2, n_features // 4),
                flip_y=noise,
                random_state=42
            )
        else:  # regression
            X, y = make_regression(
                n_samples=n_samples,
                n_features=n_features,
                noise=noise * 10,
                random_state=42
            )
        
        # Create DataFrame
        feature_names = [f'feature_{i+1}' for i in range(n_features)]
        df = pd.DataFrame(X, columns=feature_names)
        df['target'] = y
        
        # Convert to CSV
        csv_buffer = io.StringIO()
        df.to_csv(csv_buffer, index=False)
        csv_data = csv_buffer.getvalue()
        
        return jsonify({
            'csv_data': csv_data,
            'dataset_info': {
                'samples': n_samples,
                'features': n_features,
                'type': problem_type,
                'classes': n_classes if problem_type == 'classification' else 'N/A',
                'size_kb': len(csv_data) / 1024
            },
            'message': f'Generated {n_samples} samples with {n_features} features'
        }), 200
        
    except Exception as e:
        return jsonify({'error': f'Generation failed: {str(e)}'}), 500


@app.route('/api/algorithms', methods=['GET'])
@login_required
def api_get_algorithms():
    """Get list of all available algorithms organized by category"""
    category = request.args.get('category')
    
    if category:
        algorithms = registry.get_algorithms_by_category(category)
        return jsonify({'algorithms': algorithms}), 200
    else:
        # Return all algorithms organized by category
        categories = registry.get_categories()
        algorithms_by_category = {}
        for cat in categories:
            algorithms_by_category[cat] = registry.get_algorithms_by_category(cat)
        
        return jsonify({
            'categories': categories,
            'algorithms_by_category': algorithms_by_category,
            'total': len(registry.algorithms)
        }), 200


@app.route('/api/algorithm/<algorithm_name>', methods=['GET'])
@login_required
def api_get_algorithm_info(algorithm_name):
    """Get detailed algorithm information"""
    info = registry.get_algorithm_info(algorithm_name)
    
    if info:
        return jsonify(info), 200
    return jsonify({'error': 'Algorithm not found'}), 404


@app.route('/api/recommend', methods=['POST'])
@login_required
def api_recommend_algorithm():
    """Recommend algorithms based on dataset analysis or problem characteristics"""
    data = request.json
    
    # Check if dataset is provided for intelligent analysis
    if 'dataset_name' in data:
        dataset_name = data.get('dataset_name')
        task_type = data.get('task_type', 'optimization')  # optimization, feature_selection, hyperparameter
        
        # Load sample dataset for analysis
        try:
            from sklearn import datasets
            if dataset_name == 'breast_cancer':
                dataset = datasets.load_breast_cancer()
                X, y = dataset.data, dataset.target
            elif dataset_name == 'wine':
                dataset = datasets.load_wine()
                X, y = dataset.data, dataset.target
            elif dataset_name == 'iris':
                dataset = datasets.load_iris()
                X, y = dataset.data, dataset.target
            elif dataset_name == 'digits':
                dataset = datasets.load_digits()
                X, y = dataset.data, dataset.target
            elif dataset_name == 'california_housing':
                dataset = datasets.fetch_california_housing()
                X, y = dataset.data, dataset.target
            elif dataset_name == 'diabetes':
                dataset = datasets.load_diabetes()
                X, y = dataset.data, dataset.target
            else:
                return jsonify({'error': 'Unknown dataset'}), 400
            
            # Analyze dataset characteristics
            n_samples, n_features = X.shape
            dimensionality = 'low' if n_features < 10 else 'medium' if n_features < 50 else 'high'
            complexity = 'simple' if n_samples < 500 else 'medium' if n_samples < 2000 else 'complex'
            
            # Get intelligent recommendations based on task type
            if task_type == 'feature_selection':
                # Algorithms good for feature selection
                recommended = ['GWO', 'PSO', 'GA', 'WOA', 'ALO', 'SSA', 'DE', 'HHO', 'JS', 'RSA']
                reason = f'Feature selection for {n_features}-dimensional dataset with {n_samples} samples'
            elif task_type == 'hyperparameter':
                # Algorithms good for hyperparameter tuning
                recommended = ['PSO', 'GA', 'DE', 'GWO', 'WOA', 'BO', 'TPE', 'SA', 'HS', 'ES']
                reason = f'Hyperparameter optimization for ML model'
            else:
                # General optimization based on dataset characteristics
                if dimensionality == 'high':
                    recommended = ['PSO', 'DE', 'GWO', 'GA', 'WOA', 'ALO', 'JS', 'HHO', 'ChOA', 'GTO']
                    reason = f'High-dimensional optimization ({n_features} features)'
                elif complexity == 'complex':
                    recommended = ['GWO', 'WOA', 'ALO', 'PSO', 'SSA', 'HHO', 'JS', 'DE', 'GA', 'RSA']
                    reason = f'Complex optimization problem ({n_samples} samples)'
                else:
                    recommended = ['PSO', 'GWO', 'WOA', 'GA', 'DE', 'ALO', 'SSA', 'HHO', 'JS', 'RSA']
                    reason = f'General optimization ({dimensionality} dimensionality)'
            
            return jsonify({
                'recommendations': recommended[:10],
                'dataset_analysis': {
                    'samples': n_samples,
                    'features': n_features,
                    'dimensionality': dimensionality,
                    'complexity': complexity,
                    'task_type': task_type
                },
                'reason': reason
            }), 200
            
        except Exception as e:
            return jsonify({'error': f'Dataset analysis failed: {str(e)}'}), 500
    
    # Fallback to manual problem characteristics
    problem_type = data.get('problem_type', 'continuous')
    dimensions = data.get('dimensions', 10)
    has_constraints = data.get('has_constraints', False)
    multimodal = data.get('multimodal', True)
    
    recommendations = recommender.recommend(
        problem_type=problem_type,
        dimensions=dimensions,
        has_constraints=has_constraints,
        multimodal=multimodal
    )
    
    return jsonify({'recommendations': recommendations}), 200


@app.route('/api/optimize', methods=['POST'])
@login_required
def api_optimize():
    """Run optimization with multiple algorithms"""
    data = request.json
    
    try:
        # Extract parameters - handle both single algorithm and multiple
        algorithms = data.get('algorithms', [data.get('algorithm')]) if data.get('algorithms') else [data.get('algorithm')]
        if not algorithms or not algorithms[0]:
            return jsonify({'error': 'No algorithms selected'}), 400
            
        objective_func_name = data.get('objective_function')
        population_size = data.get('population_size', 30)
        max_iterations = data.get('max_iterations', 100)
        dimensions = data.get('dimensions', 10)
        lower_bound = data.get('lower_bound', -10.0)
        upper_bound = data.get('upper_bound', 10.0)
        
        # Validate
        if not objective_func_name:
            return jsonify({'error': 'Missing objective function'}), 400
        
        # Get objective function
        if objective_func_name not in OBJECTIVE_FUNCTIONS:
            return jsonify({'error': 'Invalid objective function'}), 400
        
        objective_func = OBJECTIVE_FUNCTIONS[objective_func_name]
        
        # Run all selected algorithms
        results = []
        all_convergences = []
        
        for algorithm_name in algorithms:
            try:
                # Get algorithm class using dynamic loader
                algorithm_class = load_algorithm_class(algorithm_name)
                if not algorithm_class:
                    results.append({'algorithm': algorithm_name, 'error': 'Algorithm class not found'})
                    continue
                
                # Run algorithm (handles both new and legacy patterns)
                result_model, execution_time = run_algorithm(
                    algorithm_class, objective_func,
                    population_size, max_iterations,
                    dimensions, lower_bound, upper_bound
                )
                
                # Extract results from the model
                best_position = result_model.best_solution_
                best_fitness = result_model.best_fitness_
                convergence = result_model.global_fitness_
                
                # Save result to database
                result = OptimizationResult(
                    user_id=current_user.id,
                    algorithm_name=algorithm_name,
                    objective_function=objective_func_name,
                    population_size=population_size,
                    max_iterations=max_iterations,
                    dimensions=dimensions,
                    lower_bound=lower_bound,
                    upper_bound=upper_bound,
                    best_fitness=float(best_fitness),
                    execution_time=execution_time,
                    status='completed'
                )
                result.set_best_position(best_position)
                result.set_convergence_data(convergence)
                
                save_object(result)
                
                # Prepare result object with all necessary data for comprehensive analysis
                results.append({
                    'algorithm': algorithm_name,
                    'result_id': result.id,
                    'best_fitness': float(best_fitness),
                    'mean_fitness': float(best_fitness),  # Single run, so mean = best
                    'std_fitness': 0.0,  # Single run, so std = 0
                    'execution_time': execution_time,
                    'convergence_curve': [float(f) for f in convergence] if convergence else [],
                    'best_solution': [float(p) for p in best_position] if best_position is not None else [],
                    'n_features_selected': int(sum(1 for p in best_position if p >= 0.5)) if best_position is not None else None,
                    'runs': [{
                        'best_fitness': float(best_fitness),
                        'execution_time': execution_time
                    }]  # Single run data for box plots
                })
                
                all_convergences.append({
                    'name': algorithm_name,
                    'data': convergence
                })
                
            except Exception as e:
                import traceback
                traceback.print_exc()
                results.append({'algorithm': algorithm_name, 'error': str(e)})
        
        # Return combined results
        if not results:
            return jsonify({'error': 'No algorithms could be run'}), 500
            
        # Find best result
        successful_results = [r for r in results if 'best_fitness' in r]
        if successful_results:
            best_result = min(successful_results, key=lambda x: x['best_fitness'])
            return jsonify({
                'results': results,
                'best_fitness': best_result['best_fitness'],
                'best_algorithm': best_result['algorithm'],
                'convergence_curve': all_convergences[0]['data'] if all_convergences else [],
                'all_convergences': all_convergences,
                'execution_time': sum(r['execution_time'] for r in successful_results) / len(successful_results),
                'message': f'Optimization completed for {len(successful_results)}/{len(algorithms)} algorithms'
            }), 200
        else:
            return jsonify({'error': 'All algorithms failed', 'results': results}), 500
        
    except Exception as e:
        # Log error and save failed result
        import traceback
        traceback.print_exc()
        
        result = OptimizationResult(
            user_id=current_user.id,
            algorithm_name=str(data.get('algorithms', ['unknown'])),
            objective_function=data.get('objective_function', 'unknown'),
            status='failed',
            error_message=str(e)
        )
        save_object(result)
        
        return jsonify({'error': str(e)}), 500


@app.route('/api/compare', methods=['POST'])
@login_required
def api_compare_algorithms():
    """Compare multiple algorithms"""
    data = request.json
    
    try:
        algorithms = data.get('algorithms', [])
        objective_func_name = data.get('objective_function')
        population_size = data.get('population_size', 30)
        max_iterations = data.get('max_iterations', 100)
        dimensions = data.get('dimensions', 10)
        lower_bound = data.get('lower_bound', -10.0)
        upper_bound = data.get('upper_bound', 10.0)
        
        if not algorithms or len(algorithms) < 2:
            return jsonify({'error': 'At least 2 algorithms required'}), 400
        
        if objective_func_name not in OBJECTIVE_FUNCTIONS:
            return jsonify({'error': 'Invalid objective function'}), 400
        
        objective_func = OBJECTIVE_FUNCTIONS[objective_func_name]
        
        results = []
        for algo_name in algorithms:
            try:
                algorithm_class = load_algorithm_class(algo_name)
                if not algorithm_class:
                    continue
                
                # Run algorithm (handles both new and legacy patterns)
                result_model, execution_time = run_algorithm(
                    algorithm_class, objective_func,
                    population_size, max_iterations,
                    dimensions, lower_bound, upper_bound
                )
                convergence = result_model.global_fitness_ if hasattr(result_model, 'global_fitness_') else []
                # Convert numpy arrays to lists for JSON serialization
                if hasattr(convergence, 'tolist'):
                    convergence = convergence.tolist() if hasattr(convergence, 'tolist') else list(convergence)
                else:
                    convergence = [float(x) if hasattr(x, 'item') else x for x in convergence]
                
                results.append({
                    'algorithm': algo_name,
                    'best_fitness': float(result_model.best_fitness_),
                    'execution_time': float(execution_time),
                    'convergence': convergence
                })
            except Exception as e:
                import traceback
                traceback.print_exc()
                results.append({'algorithm': algo_name, 'error': str(e)})
        
        # Save comparison session
        comparison = ComparisonSession(
            user_id=current_user.id,
            session_name=f"Comparison {datetime.now().strftime('%Y-%m-%d %H:%M')}",
            objective_function=objective_func_name,
            population_size=population_size,
            max_iterations=max_iterations,
            dimensions=dimensions
        )
        comparison.set_algorithms_tested(algorithms)
        comparison.set_results_summary(results)
        
        save_object(comparison)
        
        return jsonify({
            'comparison_id': comparison.id,
            'results': results,
            'message': 'Comparison completed successfully'
        }), 200
        
    except Exception as e:
        return jsonify({'error': str(e)}), 500


@app.route('/api/upload-algorithm', methods=['POST'])
@login_required
def api_upload_algorithm():
    """
    Upload custom algorithm - validates, maps parameters, integrates locally,
    and submits to GitHub for admin review with contributor credit.
    
    Workflow:
    1. Validate the uploaded .py file
    2. Deep-analyze variables and auto-map to standard params
    3. Generate wrapper for BaseOptimizer compatibility
    4. Save locally so the uploader can use it immediately
    5. Submit to GitHub branch for admin review
    6. Track contributor credit for future releases
    """
    try:
        if 'file' not in request.files:
            return jsonify({'error': 'No file provided'}), 400
        
        file = request.files['file']
        if file.filename == '':
            return jsonify({'error': 'No file selected'}), 400
        
        if not file.filename.endswith('.py'):
            return jsonify({'error': 'Only Python files (.py) allowed'}), 400
        
        # Read file content
        file_content = file.read().decode('utf-8')
        filename = secure_filename(file.filename)
        
        # Step 1: Validate algorithm structure
        validation = custom_manager.validate_algorithm_code(file_content)
        
        if not validation['valid']:
            return jsonify({
                'error': 'Invalid algorithm',
                'details': validation.get('errors', [])
            }), 400
        
        # Save a local copy for processing
        filepath = os.path.join(app.config['UPLOAD_FOLDER'], f"{current_user.id}_{filename}")
        with open(filepath, 'w') as f:
            f.write(file_content)
        
        # Step 2: Deep parameter analysis and auto-mapping
        process_result = custom_manager.process_upload(filepath)
        parameter_mapping = process_result.get('parameter_mapping', {})
        
        # Allow user to override mappings via form data
        user_mappings = request.form.get('parameter_mapping')
        if user_mappings:
            try:
                user_mapping_dict = json.loads(user_mappings)
                if parameter_mapping.get('mapped'):
                    parameter_mapping['mapped'].update(user_mapping_dict)
            except (json.JSONDecodeError, TypeError):
                pass
        
        # Step 3: Build contributor info
        contributor_info = {
            'author': current_user.username,
            'email': current_user.email,
            'date': datetime.now().isoformat(),
            'user_id': current_user.id
        }
        
        category = request.form.get('category', 'Custom')
        description = request.form.get('description', '')
        
        # Step 4: Complete upload (local integration + GitHub submission)
        upload_result = custom_manager.complete_upload(
            file_path=filepath,
            parameter_mapping=parameter_mapping.get('mapped', {}),
            contributor_info=contributor_info,
            category=category,
            description=description,
            metadata={
                'original_filename': filename,
                'inherits_base': validation.get('inherits_base', False),
                'parameters': validation.get('parameters', {})
            }
        )
        
        # Step 5: Save to database with credit and mapping info
        github_result = upload_result.get('github_submission', {})
        local_result = upload_result.get('local_integration', {})
        
        custom_algo = CustomAlgorithm(
            user_id=current_user.id,
            algorithm_name=validation['class_name'],
            class_name=validation['class_name'],
            filename=filename,
            file_content=file_content,
            description=description,
            category=category,
            is_validated=True,
            validation_message='Validated - available locally, awaiting admin approval for release',
            status='pending',
            github_branch=github_result.get('branch'),
            github_pr_number=github_result.get('pr_number'),
            pr_url=github_result.get('pr_url'),
            contributor_name=current_user.username,
            contributor_email=current_user.email,
            local_algo_id=local_result.get('algorithm_id')
        )
        custom_algo.set_parameter_mapping(parameter_mapping.get('mapped', {}))
        
        save_object(custom_algo)
        
        # Build response
        response_data = {
            'message': upload_result.get('message', 'Algorithm uploaded successfully!'),
            'algorithm_name': validation['class_name'],
            'algorithm_id': custom_algo.id,
            'status': 'pending',
            'local_available': local_result.get('success', False),
            'local_algo_id': local_result.get('algorithm_id'),
            'parameter_mapping': parameter_mapping,
            'contributor_credit': {
                'author': current_user.username,
                'message': (
                    f'Your algorithm is available locally on your system right now. '
                    f'It has been submitted for review. When approved and included '
                    f'in the next release, you ({current_user.username}) will be '
                    f'credited as the contributor.'
                )
            }
        }
        
        if github_result.get('success'):
            response_data['github'] = {
                'branch': github_result.get('branch'),
                'pr_url': github_result.get('pr_url'),
                'pr_number': github_result.get('pr_number')
            }
        
        if local_result.get('usage_example'):
            response_data['usage_example'] = local_result['usage_example']
        
        return jsonify(response_data), 201
        
    except Exception as e:
        return jsonify({'error': str(e)}), 500


@app.route('/api/my-custom-algorithms', methods=['GET'])
@login_required
def api_my_custom_algorithms():
    """List the current user's custom algorithms (both local and submitted)."""
    try:
        # Get from database
        db_algos = CustomAlgorithm.query.filter_by(user_id=current_user.id)\
            .order_by(CustomAlgorithm.upload_date.desc()).all()
        
        # Get from local registry
        local_algos = custom_manager.list_local_algorithms()
        
        return jsonify({
            'algorithms': [algo.to_dict() for algo in db_algos],
            'local_algorithms': local_algos,
            'total': len(db_algos)
        }), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500


@app.route('/api/run-custom-algorithm/<algo_id>', methods=['POST'])
@login_required 
def api_run_custom_algorithm(algo_id):
    """Run a local custom algorithm for the uploader."""
    try:
        # Verify the algorithm belongs to the current user
        db_algo = CustomAlgorithm.query.filter_by(
            local_algo_id=algo_id, 
            user_id=current_user.id
        ).first()
        
        if not db_algo:
            return jsonify({'error': 'Algorithm not found or access denied'}), 404
        
        # Load the algorithm
        algorithm_class = custom_manager.load_local_algorithm(algo_id)
        
        # Get optimization parameters from request
        data = request.json or {}
        pop_size = data.get('population_size', 30)
        max_iter = data.get('max_iterations', 100)
        dimensions = data.get('dimensions', 10)
        obj_func_name = data.get('objective_function', 'sphere')
        
        # Get objective function
        objective_func = OBJECTIVE_FUNCTIONS.get(obj_func_name)
        if not objective_func:
            return jsonify({'error': f'Unknown objective function: {obj_func_name}'}), 400
        
        # Run optimization
        import time
        optimizer = algorithm_class(
            population_size=pop_size, 
            max_iterations=max_iter,
            dimensions=dimensions
        )
        
        start_time = time.time()
        result = optimizer.optimize(objective_function=objective_func)
        execution_time = time.time() - start_time
        
        # Track usage
        db_algo.increment_usage()
        
        return jsonify({
            'algorithm_name': db_algo.algorithm_name,
            'best_fitness': float(result.best_fitness_),
            'best_solution': result.best_solution_.tolist()[:10],
            'execution_time': execution_time,
            'contributor': db_algo.contributor_name,
            'status': 'completed'
        }), 200
        
    except Exception as e:
        return jsonify({'error': str(e)}), 500


@app.route('/api/contributors', methods=['GET'])
def api_contributors():
    """Get all algorithm contributors and their credits."""
    try:
        # Get approved algorithms with credits
        approved = CustomAlgorithm.query.filter_by(status='approved').all()
        pending = CustomAlgorithm.query.filter_by(status='pending').count()
        
        contributors = []
        for algo in approved:
            contributors.append({
                'algorithm_name': algo.algorithm_name,
                'contributor': algo.contributor_name or (algo.user.username if algo.user else 'Unknown'),
                'category': algo.category,
                'version_included': algo.version_included,
                'upload_date': algo.upload_date.isoformat() if algo.upload_date else None
            })
        
        return jsonify({
            'contributors': contributors,
            'total_approved': len(contributors),
            'pending_review': pending
        }), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500


# Admin routes for algorithm approval
@app.route('/admin/algorithms')
@login_required
def admin_algorithms():
    """Admin view for pending algorithm submissions"""
    if not current_user.is_admin:
        flash('Access denied. Admin privileges required.', 'error')
        return redirect(url_for('dashboard'))
    
    pending = CustomAlgorithm.query.filter_by(status='pending').order_by(CustomAlgorithm.upload_date.desc()).all()
    approved = CustomAlgorithm.query.filter_by(status='approved').order_by(CustomAlgorithm.upload_date.desc()).all()
    
    return render_template('admin_algorithms.html', pending=pending, approved=approved)


@app.route('/api/admin/algorithm/<int:algo_id>/approve', methods=['POST'])
@login_required
def admin_approve_algorithm(algo_id):
    """Approve a pending algorithm and record contributor credit"""
    if not current_user.is_admin:
        return jsonify({'error': 'Admin privileges required'}), 403
    
    algo = CustomAlgorithm.query.get_or_404(algo_id)
    algo.status = 'approved'
    algo.reviewed_at = datetime.now()
    algo.reviewed_by = current_user.id
    algo.admin_notes = request.json.get('notes', '') if request.json else ''
    algo.credit_acknowledged = True
    
    # Set the version where this will be included
    from mha_toolbox import __version__
    algo.version_included = request.json.get('version', __version__) if request.json else __version__
    
    # Load algorithm into the published registry
    try:
        filepath = os.path.join(app.config['UPLOAD_FOLDER'], f"{algo.user_id}_{algo.filename}")
        if os.path.exists(filepath):
            custom_manager.integrate_algorithm(
                filepath, 
                algo.get_parameter_mapping(),
                category=algo.category or 'custom',
                metadata={
                    'author': algo.contributor_name or (algo.user.username if algo.user else 'Unknown'),
                    'email': algo.contributor_email or '',
                    'upload_date': algo.upload_date.isoformat() if algo.upload_date else '',
                    'version_included': algo.version_included,
                    'description': algo.description
                }
            )
    except Exception as e:
        pass  # Non-blocking: admin can still approve
    
    save_object(algo)
    
    return jsonify({
        'message': f'Algorithm {algo.algorithm_name} approved! Credit given to {algo.contributor_name or "contributor"}.',
        'status': 'approved',
        'contributor': algo.contributor_name,
        'version_included': algo.version_included
    })


@app.route('/api/admin/algorithm/<int:algo_id>/reject', methods=['POST'])
@login_required
def admin_reject_algorithm(algo_id):
    """Reject a pending algorithm"""
    if not current_user.is_admin:
        return jsonify({'error': 'Admin privileges required'}), 403
    
    algo = CustomAlgorithm.query.get_or_404(algo_id)
    algo.status = 'rejected'
    algo.reviewed_at = datetime.now()
    algo.reviewed_by = current_user.id
    algo.admin_notes = request.json.get('notes', 'Rejected by admin') if request.json else 'Rejected by admin'
    
    save_object(algo)
    
    return jsonify({
        'message': f'Algorithm {algo.algorithm_name} rejected',
        'status': 'rejected'
    })


@app.route('/api/admin/algorithm/<int:algo_id>/code', methods=['GET'])
@login_required
def admin_view_algorithm_code(algo_id):
    """View algorithm code (admin only)"""
    if not current_user.is_admin:
        return jsonify({'error': 'Admin privileges required'}), 403
    
    algo = CustomAlgorithm.query.get_or_404(algo_id)
    return jsonify({
        'algorithm_name': algo.algorithm_name,
        'code': algo.file_content,
        'description': algo.description,
        'author': algo.user.username if hasattr(algo, 'user') and algo.user else 'Unknown'
    })


@app.route('/api/hyperparameter-tune', methods=['POST'])
@login_required
def api_hyperparameter_tune():
    """Hyperparameter tuning"""
    data = request.json
    
    try:
        algorithm_name = data.get('algorithm')
        objective_func_name = data.get('objective_function')
        method = data.get('method', 'grid')  # grid, random, bayesian
        param_ranges = data.get('param_ranges', {})
        
        if not algorithm_name or not objective_func_name:
            return jsonify({'error': 'Missing required parameters'}), 400
        
        objective_func = OBJECTIVE_FUNCTIONS.get(objective_func_name)
        if not objective_func:
            return jsonify({'error': 'Invalid objective function'}), 400
        
        algorithm_class = registry.get_algorithm(algorithm_name)
        if not algorithm_class:
            return jsonify({'error': 'Algorithm not found'}), 404
        
        # Initialize tuner
        tuner = HyperparameterTuner(algorithm_class, objective_func)
        
        # Run tuning
        start_time = time.time()
        best_params, best_score, history = tuner.tune(
            method=method,
            param_ranges=param_ranges,
            dimensions=data.get('dimensions', 10),
            n_iter=data.get('n_iterations', 10)
        )
        execution_time = time.time() - start_time
        
        return jsonify({
            'best_params': best_params,
            'best_score': float(best_score),
            'history': history,
            'execution_time': execution_time,
            'message': 'Hyperparameter tuning completed'
        }), 200
        
    except Exception as e:
        return jsonify({'error': str(e)}), 500


@app.route('/api/statistics', methods=['GET'])
@login_required
def api_statistics():
    """Get user statistics"""
    stats = {
        'total_optimizations': OptimizationResult.query.filter_by(user_id=current_user.id).count(),
        'total_custom_algorithms': CustomAlgorithm.query.filter_by(user_id=current_user.id).count(),
        'total_comparisons': ComparisonSession.query.filter_by(user_id=current_user.id).count(),
        'most_used_algorithm': (lambda row: {'name': row[0], 'count': row[1]} if row else None)(
            db.session.query(
                OptimizationResult.algorithm_name,
                db.func.count(OptimizationResult.id).label('count')
            ).filter_by(user_id=current_user.id)\
             .group_by(OptimizationResult.algorithm_name)\
             .order_by(db.desc('count')).first()
        ),
        'average_execution_time': db.session.query(
            db.func.avg(OptimizationResult.execution_time)
        ).filter_by(user_id=current_user.id).scalar() or 0
    }
    
    return jsonify(stats), 200


# ============================================================================
# HEALTH & UTILITY API ENDPOINTS
# ============================================================================

@app.route('/api/health', methods=['GET'])
def api_health():
    """Health check endpoint for server status"""
    return jsonify({
        'status': 'healthy',
        'version': '2.0.8',
        'server': 'MHA Flow Production',
        'timestamp': datetime.utcnow().isoformat()
    }), 200


@app.route('/api/algorithms/list', methods=['GET'])
@login_required
def api_algorithms_list():
    """Get flat list of all algorithm names (for dropdowns)"""
    try:
        algorithms = sorted(registry.algorithms.keys())
        return jsonify({'algorithms': algorithms}), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500


@app.route('/api/algorithms/characteristics/<algorithm_name>', methods=['GET'])
@login_required
def api_algorithm_characteristics(algorithm_name):
    """Get algorithm characteristics for levify system"""
    try:
        from mha_toolbox.levify import LevifyTransformer
        transformer = LevifyTransformer()
        chars = transformer.get_algorithm_characteristics(algorithm_name)
        if chars:
            return jsonify(chars.to_dict()), 200
        # Return default characteristics if not profiled
        return jsonify({
            'name': algorithm_name,
            'exploration_score': 0.5,
            'exploitation_score': 0.5,
            'convergence_speed': 0.5,
            'diversity_maintenance': 0.5,
            'local_optima_avoidance': 0.5,
            'phase': 'balanced',
            'parameter_count': 3,
            'dimension_scalability': 'medium'
        }), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500


@app.route('/api/levify/transform', methods=['POST'])
@login_required
def api_levify_transform():
    """Apply levify transformation to an algorithm"""
    try:
        from mha_toolbox.levify import LevifyTransformer
        data = request.json
        algorithm = data.get('algorithm', '')
        transform_type = data.get('type', 'exploration')
        intensity = float(data.get('intensity', 0.5))

        transformer = LevifyTransformer()

        if transform_type == 'exploration':
            result = transformer.levify_to_exploration(algorithm, intensity)
        elif transform_type == 'exploitation':
            result = transformer.levify_to_exploitation(algorithm, intensity)
        else:
            result = transformer.levify_to_exploration(algorithm, intensity)

        if 'error' in result:
            return jsonify({'success': False, 'error': result['error']}), 400

        return jsonify({
            'success': True,
            'transformation': result
        }), 200
    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500


@app.route('/api/dataset/upload', methods=['POST'])
@login_required
def api_dataset_upload():
    """Upload and analyze a dataset file"""
    try:
        if 'file' not in request.files:
            return jsonify({'success': False, 'error': 'No file provided'}), 400

        file = request.files['file']
        if file.filename == '':
            return jsonify({'success': False, 'error': 'No file selected'}), 400

        problem_type = request.form.get('problem_type', 'optimization')
        filename = secure_filename(file.filename)

        # Read dataset based on extension
        import pandas as pd
        import io

        if filename.endswith('.csv'):
            df = pd.read_csv(io.BytesIO(file.read()))
        elif filename.endswith(('.xls', '.xlsx')):
            df = pd.read_excel(io.BytesIO(file.read()))
        elif filename.endswith('.json'):
            df = pd.read_json(io.BytesIO(file.read()))
        else:
            return jsonify({'success': False, 'error': 'Unsupported file format. Use CSV, Excel, or JSON.'}), 400

        # Analyze using DatasetIntelligence
        from mha_toolbox.dataset_intelligence import DatasetIntelligence
        intelligence = DatasetIntelligence()

        # Separate features and target (last column as target)
        if df.shape[1] > 1:
            X = df.iloc[:, :-1].values
            y = df.iloc[:, -1].values
        else:
            X = df.values
            y = None

        profile = intelligence.analyze_dataset(X, target=y, dataset_name=filename, problem_type=problem_type)
        report = intelligence.get_intelligence_report(profile)

        # Store dataset info in session for later use
        session['dataset_profile'] = {
            'filename': filename,
            'num_samples': int(profile.num_samples),
            'dimensions': int(profile.dimensions)
        }

        return jsonify({
            'success': True,
            'profile': profile.to_dict(),
            'report': report
        }), 200

    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500


@app.route('/api/dataset/profile', methods=['GET'])
@login_required
def api_dataset_profile():
    """Get the current dataset profile from session"""
    profile = session.get('dataset_profile')
    if not profile:
        return jsonify({'error': 'No dataset loaded'}), 400
    return jsonify(profile), 200


# ============================================================================
# ERROR HANDLERS
# ============================================================================

@app.errorhandler(404)
def not_found(error):
    """404 error handler"""
    if request.path.startswith('/api/'):
        return jsonify({'error': 'Not found'}), 404
    return render_template('404.html'), 404


@app.errorhandler(500)
def internal_error(error):
    """500 error handler"""
    db.session.rollback()
    if request.path.startswith('/api/'):
        return jsonify({'error': 'Internal server error'}), 500
    return render_template('500.html'), 500


# ============================================================================
# DATABASE INITIALIZATION
# ============================================================================

@app.cli.command()
def init_db():
    """Initialize database"""
    db.create_all()


@app.cli.command()
def create_admin():
    """Create admin user"""
    username = input("Admin username: ")
    email = input("Admin email: ")
    password = input("Admin password: ")
    
    admin = User(username=username, email=email, is_admin=True)
    admin.set_password(password)
    
    save_object(admin)
    


if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    
    app.run(debug=True, host='0.0.0.0', port=5000)
