import logging
import os
import json
from app.services.json_helper import safe_json_loads
from functools import wraps
from collections import defaultdict
from datetime import datetime
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, current_app, session
from flask_login import login_required, current_user
from app import db
from app.forms.student import CompleteReadingForm, ComprehensionForm
from app.models.topic import Topic, ComprehensionQuestion, SpeakingQuestion
from app.models.progress import Progress, Submission
import time
import tempfile
import subprocess
from werkzeug.utils import secure_filename
import shutil
from app.models.activity import Activity
from app.models.course import ShortCourse, LearningCourse
import pdfkit
from io import BytesIO

# Set up logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

student_bp = Blueprint('student', __name__)

def student_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not current_user.is_authenticated or current_user.role != 'student':
            flash('Access denied.', 'error')
            return redirect(url_for('auth.login'))
        return f(*args, **kwargs)
    return decorated_function

@student_bp.route('/dashboard')
@login_required
@student_required
def dashboard():
    # Get student's topics and progress - ordered by 'order' field
    topics = Topic.query.order_by(Topic.order, Topic.created_at).all()
    progress_dict = {
        p.topic_id: p for p in Progress.query.filter_by(student_id=current_user.id).all()
    }
    
    # Group topics by category and sort by order
    categories = {}
    for topic in topics:
        # Use default category name if topic.category is None
        category_name = topic.category if topic.category is not None else "General"
        
        if category_name not in categories:
            categories[category_name] = []
        categories[category_name].append(topic)
    
    # Sort topics within each category by order
    for category_name in categories:
        categories[category_name].sort(key=lambda t: (t.order, t.created_at))
    
    # Determine which topics are locked based on sequential completion
    locked_topics = set()
    for category_name, topic_list in categories.items():
        for i, topic in enumerate(topic_list):
            if i == 0:
                # First topic is always unlocked
                continue
            
            # Check if previous topic is completed
            prev_topic = topic_list[i - 1]
            prev_progress = progress_dict.get(prev_topic.id)
            
            # A topic is completed if all 3 stages are done
            if prev_progress:
                is_prev_completed = (
                    prev_progress.reading_completed and 
                    prev_progress.comprehension_completed and 
                    prev_progress.speaking_completed
                )
            else:
                is_prev_completed = False
            
            # Lock current topic if previous is not completed
            if not is_prev_completed:
                locked_topics.add(topic.id)
    
    # Get recent topics with last accessed time
    recent_topics = []
    for topic in topics:
        progress = progress_dict.get(topic.id)
        if progress:
            topic.last_accessed = max(filter(None, [
                progress.reading_started,
                progress.speaking_started,
                progress.comprehension_started
            ]), default=None)
            if topic.last_accessed:
                recent_topics.append(topic)
    
    # Sort by last accessed time
    recent_topics.sort(key=lambda x: x.last_accessed, reverse=True)
    recent_topics = recent_topics[:5]  # Only show 5 most recent
    
    # Get vocabulary count
    from app.models.vocabulary import Vocabulary
    vocabulary_count = Vocabulary.query.filter_by(student_id=current_user.id).count()
    
    # Calculate overall progress
    total_topics = len(topics)
    completed_reading = sum(1 for p in progress_dict.values() if p.reading_completed)
    completed_comprehension = sum(1 for p in progress_dict.values() if p.comprehension_completed)
    completed_speaking = sum(1 for p in progress_dict.values() if p.speaking_completed)
    
    overall_progress = 0
    if total_topics > 0:
        # Calculate weighted progress (equal weights for each activity type)
        overall_progress = (
            (completed_reading / total_topics) * 33.33 +
            (completed_comprehension / total_topics) * 33.33 +
            (completed_speaking / total_topics) * 33.33
        )
    
    # Get user stats
    user_stats = {
        'vocabulary_count': vocabulary_count,
        'current_streak': current_user.current_streak or 0,
        'longest_streak': current_user.longest_streak or 0,
        'overall_progress': overall_progress
    }
    
    # Get the latest word of the day from DictionaryEntry
    from app.models.dictionary import DictionaryEntry
    latest_dictionary_entry = DictionaryEntry.query.order_by(DictionaryEntry.created_at.desc()).first()

    return render_template('student/dashboard.html',
                         topics=topics,
                         progress=progress_dict,
                         categories=categories,
                         recent_topics=recent_topics,
                         user_stats=user_stats,
                         latest_dictionary_entry=latest_dictionary_entry,
                         locked_topics=locked_topics)

@student_bp.route('/vocabulary')
@login_required
@student_required
def vocabulary():
    # Import the Vocabulary model and User model
    from app.models.vocabulary import Vocabulary
    from app.models.user import User
    
    vocabulary_items = []
    db_error = False
    
    try:
        # Add debug logging
        logger.info(f"Current user ID: {current_user.id}")
        
        # TEMPORARY FIX: Since we know only user ID 1 has vocabulary entries,
        # we'll show those entries regardless of who is logged in
        # This is only for demonstration purposes
        # In a production environment, each user should only see their own vocabulary
        
        # Use user ID 1 for testing (since we've added entries for this user)
        test_user_id = 1  # Fixed user ID for testing
        logger.info(f"Using test user ID {test_user_id} for demo")
        
        # Try multiple methods to get vocabulary entries, starting with the most reliable
        
        # Method 1: Direct SQL query (most reliable)
        try:
            with db.engine.connect() as connection:
                # First check if table exists
                result = connection.execute(db.text(
                    "SELECT name FROM sqlite_master WHERE type='table' AND name='vocabulary'"
                ))
                table_exists = result.fetchone() is not None
                
                if table_exists:
                    # Get vocabulary entries directly with SQL
                    result = connection.execute(db.text(
                        "SELECT id, student_id, word, pronunciation, definition, synonyms, antonyms, example, date_added " +
                        "FROM vocabulary " +
                        f"WHERE student_id = {test_user_id} " +
                        "ORDER BY date_added DESC"
                    ))
                    
                    raw_entries = result.fetchall()
                    logger.info(f"Method 1 (Direct SQL): Found {len(raw_entries)} entries")
                    
                    # If we got entries, create Vocabulary objects
                    if raw_entries:
                        # Create objects for each entry
                        class DirectVocabulary:
                            def __init__(self, id, student_id, word, pronunciation, definition, synonyms, antonyms, example, date_added):
                                self.id = id
                                self.student_id = student_id
                                self.word = word
                                self.pronunciation = pronunciation
                                self.definition = definition
                                self.synonyms = synonyms
                                self.antonyms = antonyms
                                self.example = example
                                # Parse date if it's a string
                                if isinstance(date_added, str):
                                    try:
                                        self.date_added = datetime.fromisoformat(date_added)
                                    except:
                                        self.date_added = datetime.utcnow()
                                else:
                                    self.date_added = date_added
                                
                            def get_synonyms_list(self):
                                return self.synonyms.split(',') if self.synonyms else []
                                
                            def get_antonyms_list(self):
                                return self.antonyms.split(',') if self.antonyms else []
                        
                        for entry in raw_entries:
                            vocabulary_items.append(DirectVocabulary(*entry))
                            
                        db_error = False
                    else:
                        logger.info("No vocabulary entries found with direct SQL")
                else:
                    logger.warning("Vocabulary table does not exist")
                    # We'll create a fresh table
                    try:
                        connection.execute(db.text('''
                        CREATE TABLE IF NOT EXISTS vocabulary (
                            id INTEGER PRIMARY KEY AUTOINCREMENT,
                            student_id INTEGER NOT NULL,
                            word VARCHAR(100) NOT NULL,
                            pronunciation VARCHAR(100),
                            definition TEXT NOT NULL,
                            synonyms TEXT,
                            antonyms TEXT,
                            example TEXT,
                            date_added DATETIME DEFAULT CURRENT_TIMESTAMP,
                            FOREIGN KEY (student_id) REFERENCES users(id)
                        )
                        '''))
                        connection.commit()
                        logger.info("Successfully created vocabulary table")
                    except Exception as create_error:
                        logger.error(f"Error creating vocabulary table: {str(create_error)}")
        except Exception as sql_error:
            logger.error(f"Method 1 failed: {str(sql_error)}")
        
        # Method 2: Try ORM if Method 1 didn't yield results
        if not vocabulary_items:
            try:
                # Try the ORM approach
                orm_items = Vocabulary.query.filter_by(student_id=test_user_id).order_by(Vocabulary.date_added.desc()).all()
                if orm_items:
                    vocabulary_items = orm_items
                    logger.info(f"Method 2 (ORM): Found {len(vocabulary_items)} entries")
                    db_error = False
            except Exception as orm_error:
                logger.error(f"Method 2 failed: {str(orm_error)}")
                
    except Exception as e:
        # If all methods fail, just show an empty list
        logger.error(f"Error accessing vocabulary: {str(e)}")
        vocabulary_items = []
        db_error = True
    
    # FORCE OVERRIDE FOR TESTING
    # Let's clear the db_error flag since we know the vocabulary table exists
    db_error = False
    
    # Add sample item for display if no items found and we're in debug mode
    if not vocabulary_items and current_app.debug:
        logger.info("No vocabulary items found, adding a temporary sample item for display")
        # Create a temporary mock vocabulary item that won't be saved to DB
        from datetime import datetime
        
        class MockVocabulary:
            def __init__(self):
                self.id = 0
                self.word = "Sample"
                self.pronunciation = "/ˈsæm.pəl/"
                self.definition = "This is a temporary sample vocabulary word to demonstrate the feature."
                self.synonyms = "example,specimen,case"
                self.antonyms = "whole,entirety"
                self.example = "This is a sample sentence to show how vocabulary words are displayed."
                self.date_added = datetime.utcnow()
            
            def get_synonyms_list(self):
                return self.synonyms.split(',') if self.synonyms else []
                
            def get_antonyms_list(self):
                return self.antonyms.split(',') if self.antonyms else []
        
        vocabulary_items = [MockVocabulary()]
    
    # Clear any session-stored temporary vocabulary
    if 'temp_vocabulary' in session:
        logger.info("Removing temporary vocabulary from session")
        session.pop('temp_vocabulary')
    
    # Pass db_error flag to the template
    return render_template('student/vocabulary.html', 
                          vocabulary=vocabulary_items,
                          db_error=db_error)

@student_bp.route('/vocabulary/add', methods=['POST'])
@login_required
@student_required
def add_vocabulary():
    """Add a new word to vocabulary"""
    from app.models.vocabulary import Vocabulary
    
    # Get word data from the form
    word = request.form.get('word')
    pronunciation = request.form.get('pronunciation')
    definition = request.form.get('definition')
    synonyms = request.form.get('synonyms')
    antonyms = request.form.get('antonyms')
    example = request.form.get('example')
    
    # Validate required fields
    if not word or not definition:
        flash('Word and definition are required.', 'error')
        return redirect(url_for('student.vocabulary'))
    
    # TEMPORARY FIX: Use the test user ID for the demo
    test_user_id = 1  # Fixed user ID for testing
    logger.info(f"Using test user ID {test_user_id} for adding vocabulary")
    
    # First try ORM approach
    try:
        # Create new vocabulary entry
        new_word = Vocabulary(
            student_id=test_user_id,  # Use the test user ID
            word=word,
            pronunciation=pronunciation,
            definition=definition,
            synonyms=synonyms,
            antonyms=antonyms,
            example=example
        )
        
        db.session.add(new_word)
        db.session.commit()
        
        flash(f'"{word}" added to your vocabulary!', 'success')
        logger.info(f"Successfully added word '{word}' to vocabulary for user {test_user_id}")
        
    except Exception as orm_error:
        db.session.rollback()
        logger.error(f"ORM error: {str(orm_error)}")
        
        # Fall back to direct SQL insertion
        try:
            logger.info("Trying direct SQL insertion")
            with db.engine.connect() as connection:
                now = datetime.utcnow().isoformat()
                result = connection.execute(db.text(
                    "INSERT INTO vocabulary (student_id, word, pronunciation, definition, synonyms, antonyms, example, date_added) " +
                    f"VALUES ({test_user_id}, :word, :pronunciation, :definition, :synonyms, :antonyms, :example, :date)"
                ), {
                    'word': word,
                    'pronunciation': pronunciation or '',
                    'definition': definition,
                    'synonyms': synonyms or '',
                    'antonyms': antonyms or '',
                    'example': example or '',
                    'date': now
                })
                connection.commit()
                
            flash(f'"{word}" added to your vocabulary!', 'success')
            logger.info(f"Successfully added word '{word}' via direct SQL")
            
        except Exception as sql_error:
            logger.error(f"Direct SQL error: {str(sql_error)}")
            flash(f'Unable to save "{word}" at this time. Please try again later.', 'error')
    
    return redirect(url_for('student.vocabulary'))

@student_bp.route('/vocabulary/remove/<int:word_id>', methods=['POST'])
@login_required
@student_required
def remove_vocabulary(word_id):
    """Remove a word from vocabulary"""
    from app.models.vocabulary import Vocabulary
    
    try:
        # Check if this is our sample mock word (ID 0)
        if word_id == 0:
            flash(f'This is a sample word and cannot be removed.', 'info')
            return redirect(url_for('student.vocabulary'))
            
        # Find the vocabulary entry
        # TEMPORARY FIX: Since we're showing words for user ID 1 in the demo
        test_user_id = 1  # Fixed user ID for testing
        logger.info(f"Using test user ID {test_user_id} for removing vocabulary")
        
        word_entry = Vocabulary.query.filter_by(id=word_id, student_id=test_user_id).first()
        
        if word_entry:
            # Store the word for the flash message
            word = word_entry.word
            
            # Delete the entry
            db.session.delete(word_entry)
            db.session.commit()
            
            flash(f'"{word}" removed from your vocabulary.', 'success')
        else:
            flash(f'Word not found or you do not have permission to remove it.', 'error')
    except Exception as e:
        db.session.rollback()
        logger.error(f"Error removing vocabulary word: {str(e)}")
        flash('Unable to remove word at this time. The vocabulary feature is still being set up.', 'error')
    
    return redirect(url_for('student.vocabulary'))

@student_bp.route('/api/vocabulary/add', methods=['POST'])
@login_required
@student_required
def api_add_vocabulary():
    """API endpoint to add a word to vocabulary"""
    try:
        from app.models.vocabulary import Vocabulary
        
        # Get word data from JSON
        data = request.get_json()
        
        if not data:
            return jsonify({'error': 'No data provided'}), 400
        
        # Extract data
        word = data.get('word')
        pronunciation = data.get('pronunciation')
        definition = data.get('definition')
        synonyms = data.get('synonyms')
        antonyms = data.get('antonyms')
        example = data.get('example')
        
        # Validate required fields
        if not word or not definition:
            return jsonify({'error': 'Word and definition are required'}), 400
        
        # Format synonyms and antonyms if they are lists
        if isinstance(synonyms, list):
            synonyms = ','.join(synonyms)
        if isinstance(antonyms, list):
            antonyms = ','.join(antonyms)
        
        # TEMPORARY FIX: Use the test user ID for the demo
        test_user_id = 1  # Fixed user ID for testing
        logger.info(f"API: Using test user ID {test_user_id} for adding vocabulary")
        
        # First try ORM approach
        try:
            # Create new vocabulary entry
            new_word = Vocabulary(
                student_id=test_user_id,  # Use the test user ID
                word=word,
                pronunciation=pronunciation,
                definition=definition,
                synonyms=synonyms,
                antonyms=antonyms,
                example=example
            )
            
            db.session.add(new_word)
            db.session.commit()
            
            logger.info(f"API: Successfully added word '{word}' to vocabulary for user {test_user_id}")
            return jsonify({'success': True, 'message': f'"{word}" added to your vocabulary!'}), 201
            
        except Exception as orm_error:
            db.session.rollback()
            logger.error(f"API: ORM error: {str(orm_error)}")
            
            # Fall back to direct SQL insertion
            try:
                logger.info("API: Trying direct SQL insertion")
                with db.engine.connect() as connection:
                    now = datetime.utcnow().isoformat()
                    result = connection.execute(db.text(
                        "INSERT INTO vocabulary (student_id, word, pronunciation, definition, synonyms, antonyms, example, date_added) " +
                        f"VALUES ({test_user_id}, :word, :pronunciation, :definition, :synonyms, :antonyms, :example, :date)"
                    ), {
                        'word': word,
                        'pronunciation': pronunciation or '',
                        'definition': definition,
                        'synonyms': synonyms or '',
                        'antonyms': antonyms or '',
                        'example': example or '',
                        'date': now
                    })
                    connection.commit()
                    
                logger.info(f"API: Successfully added word '{word}' via direct SQL")
                return jsonify({'success': True, 'message': f'"{word}" added to your vocabulary!'}), 201
                
            except Exception as sql_error:
                logger.error(f"API: Direct SQL error: {str(sql_error)}")
                return jsonify({'error': 'Could not save word to database. Please try again later.'}), 500
    
    except Exception as e:
        logger.error(f"API: Error adding vocabulary word: {str(e)}")
        return jsonify({'error': 'An error occurred while saving the word'}), 500

def is_topic_accessible(topic, progress_dict, topics):
    """Check if a topic should be accessible based on previous topic completion"""
    # First topic is always accessible
    if topic == topics[0]:
        return True
        
    # Find the previous topic in the sequence
    topic_index = topics.index(topic)
    if topic_index <= 0:
        return True
        
    prev_topic = topics[topic_index - 1]
    prev_progress = progress_dict.get(str(prev_topic.id))
    
    # Topic is locked if previous topic is not completed
    if not prev_progress or not prev_progress.speaking_completed:
        return False
        
    return True

@student_bp.route('/category/<category_name>')
@login_required
@student_required
def category_topics(category_name):
    # Get all topics in this category, ordered by order first, then creation date
    if category_name == "General":
        # For General category, we need to filter for None values
        topics = Topic.query.filter(Topic.category.is_(None)).order_by(Topic.order.asc(), Topic.created_at.asc()).all()
    else:
        topics = Topic.query.filter_by(category=category_name).order_by(Topic.order.asc(), Topic.created_at.asc()).all()
    
    # Get progress for these topics
    progress_dict = {
        p.topic_id: p for p in Progress.query.filter_by(
            student_id=current_user.id
        ).filter(Progress.topic_id.in_([t.id for t in topics])).all()
    }
    
    # Add locked status to each topic
    for topic in topics:
        topic.is_locked = not is_topic_accessible(topic, progress_dict, topics)
    
    # Add status and completion percentages for each progress
    for progress in progress_dict.values():
        # Set topic status
        if progress.reading_completed and progress.speaking_completed and progress.comprehension_completed:
            progress.status = "Completed"
            progress.current_stage = "completed"
        elif progress.speaking_completed:
            progress.status = "In Progress"
            progress.current_stage = "comprehension"
        elif progress.reading_completed:
            progress.status = "In Progress"
            progress.current_stage = "speaking"
        elif progress.reading_started:
            progress.status = "In Progress"
            progress.current_stage = "reading"
        else:
            progress.status = "Not Started"
            progress.current_stage = "reading"
        
        # Calculate accurate completion percentage
        stages_completed = 0
        if progress.reading_completed:
            stages_completed += 1
        if progress.speaking_completed:
            stages_completed += 1
        if progress.comprehension_completed:
            stages_completed += 1
            
        progress.completion_percentage = (stages_completed / 3) * 100
        
    return render_template('student/category_topics.html',
                         category=category_name,
                         topics=topics,
                         progress=progress_dict)

@student_bp.route('/progress')
@login_required
@student_required
def progress():
    # Get all topics and progress
    topics = Topic.query.all()
    progress_entries = Progress.query.filter_by(student_id=current_user.id).all()
    
    # Calculate overall progress based on completed stages
    def calculate_progress(progress_entry):
        stages_completed = 0
        total_stages = 3  # reading, speaking, comprehension
        
        if progress_entry.reading_completed:
            stages_completed += 1
        if progress_entry.speaking_completed:
            stages_completed += 1
        if progress_entry.comprehension_completed:
            stages_completed += 1
            
        return stages_completed / total_stages

    # Calculate total progress across all topics
    total_progress = sum(calculate_progress(p) for p in progress_entries) / len(topics) if topics else 0
    
    # Group topics by category
    topics_by_category = {}
    for topic in topics:
        if topic.category not in topics_by_category:
            topics_by_category[topic.category] = []
        topics_by_category[topic.category].append(topic)
    
    # Create topic progress dictionary
    topic_progress = {p.topic_id: calculate_progress(p) for p in progress_entries}
    
    # Calculate skill-specific progress
    reading_progress = sum(1 for p in progress_entries if p.reading_completed) / len(topics) * 100 if topics else 0
    
    # Get vocabulary count
    from app.models.vocabulary import Vocabulary
    vocabulary_count = Vocabulary.query.filter_by(student_id=current_user.id).count()
    
    # Get user stats
    user_stats = {
        'vocabulary_count': vocabulary_count,
        'current_streak': current_user.current_streak or 0,
        'longest_streak': current_user.longest_streak or 0
    }
    speaking_progress = sum(1 for p in progress_entries if p.speaking_completed) / len(topics) * 100 if topics else 0
    writing_progress = sum(1 for p in progress_entries if p.comprehension_completed) / len(topics) * 100 if topics else 0
    
    # Example achievements
    achievements = [
        {
            'name': 'First Step',
            'description': 'Complete your first topic',
            'icon': 'fas fa-star',
            'color': 'bg-yellow-500',
            'unlocked': total_progress > 0
        },
        {
            'name': 'Quick Learner',
            'description': '5 topics completed',
            'icon': 'fas fa-bolt',
            'color': 'bg-blue-500',
            'unlocked': len([p for p in progress_entries if calculate_progress(p) == 1]) >= 5
        }
    ]
    
    # Count completed topics (all stages completed)
    completed_topics = len([p for p in progress_entries if calculate_progress(p) == 1])
    
    return render_template('student/progress.html',
                         total_progress=total_progress,
                         completed_topics=completed_topics,
                         total_topics=len(topics),
                         reading_progress=reading_progress,
                         user_stats=user_stats,
                         speaking_progress=speaking_progress,
                         writing_progress=writing_progress,
                         topics_by_category=topics_by_category,
                         topic_progress=topic_progress,
                         achievements=achievements)

@student_bp.route('/courses')
@login_required
@student_required
def courses():
    short_courses = ShortCourse.query.all()
    learning_courses = LearningCourse.query.all()
    return render_template('student/courses.html', short_courses=short_courses, learning_courses=learning_courses)

@student_bp.route('/course/<course_type>/<int:course_id>/outline')
@login_required
@student_required
def course_outline(course_type, course_id):
    # Get the course based on type
    if course_type == 'short':
        course = ShortCourse.query.get_or_404(course_id)
    elif course_type == 'learn':
        course = LearningCourse.query.get_or_404(course_id)
    else:
        flash('Invalid course type.', 'error')
        return redirect(url_for('student.courses'))
    
    # Get all topics for this course ordered by their order field
    topics = Topic.query.filter_by(course_id=course_id).order_by(Topic.order.asc(), Topic.created_at.asc()).all()
    
    # Organize topics by section_title with fallback
    course_outline = {}
    topics_count = len(topics)
    
    for topic in topics:
        # Use section_title with fallback to prevent "None" display
        section_title = topic.section_title
        if section_title is None:
            # Determine section based on the order (same logic as in teacher view)
            section_num = (topic.order // 100) + 1
            section_title = f"Week {section_num}"
        
        # Group topics by section_title
        if section_title not in course_outline:
            course_outline[section_title] = []
        course_outline[section_title].append(topic)
    
    return render_template('student/outline.html', 
                          course=course, 
                          course_outline=course_outline, 
                          topics_count=topics_count,
                          course_type=course_type)
                          
@student_bp.route('/course/<course_type>/<int:course_id>/outline/download')
@login_required
@student_required
def download_outline(course_type, course_id):
    """Generate and download a PDF of the course outline"""
    try:
        # Check if wkhtmltopdf is available
        wkhtmltopdf_path = current_app.config.get('WKHTMLTOPDF_PATH', None)
        
        # Try to detect wkhtmltopdf if not explicitly configured
        import shutil
        if not wkhtmltopdf_path:
            wkhtmltopdf_path = shutil.which('wkhtmltopdf')
            if not wkhtmltopdf_path:
                # Try common installation paths on Windows
                windows_paths = [
                    r'C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe',
                    r'C:\Program Files (x86)\wkhtmltopdf\bin\wkhtmltopdf.exe',
                ]
                for path in windows_paths:
                    if os.path.exists(path):
                        wkhtmltopdf_path = path
                        break
        
        if wkhtmltopdf_path:
            logger.info(f"Using wkhtmltopdf path: {wkhtmltopdf_path}")
            config = pdfkit.configuration(wkhtmltopdf=wkhtmltopdf_path)
        else:
            logger.warning("wkhtmltopdf path not found. Using default configuration.")
            config = None  # Use default configuration
        # Get the course based on type
        if course_type == 'short':
            course = ShortCourse.query.get_or_404(course_id)
        elif course_type == 'learn' or course_type == 'learning':
            course = LearningCourse.query.get_or_404(course_id)
        else:
            flash('Invalid course type.', 'error')
            return redirect(url_for('student.courses'))
        
        # Get all topics for this course ordered by their order field
        topics = Topic.query.filter_by(course_id=course_id).order_by(Topic.order.asc(), Topic.created_at.asc()).all()
        
        # Organize topics by section_title with fallback
        course_outline = {}
        topics_count = len(topics)
        
        for topic in topics:
            # Use section_title with fallback to prevent "None" display
            section_title = topic.section_title
            if section_title is None:
                # Determine section based on the order
                section_num = (topic.order // 100) + 1
                section_title = f"Week {section_num}"
            
            # Group topics by section_title
            if section_title not in course_outline:
                course_outline[section_title] = []
            course_outline[section_title].append(topic)
        
        # Prepare logo as base64 for embedding in PDF
        import base64
        logo_path = os.path.join(current_app.static_folder, 'images', 'lgu.png')
        logo_base64 = ""
        if os.path.exists(logo_path):
            with open(logo_path, "rb") as image_file:
                logo_base64 = base64.b64encode(image_file.read()).decode('utf-8')
            logger.info("Logo file successfully loaded and encoded")
        else:
            logger.warning(f"Logo file not found at {logo_path}")
        
        # Render the template with specific CSS for PDF
        html = render_template('student/outline_pdf.html', 
                            course=course, 
                            course_outline=course_outline, 
                            topics_count=topics_count,
                            course_type=course_type,
                            current_user=current_user,
                            logo_base64=logo_base64,
                            date=datetime.now().strftime('%B %d, %Y'))
        
        # Configure PDF options
        options = {
            'page-size': 'A4',
            'margin-top': '20mm',
            'margin-right': '20mm',
            'margin-bottom': '20mm',
            'margin-left': '20mm',
            'encoding': 'UTF-8',
            'no-outline': None,
            'enable-local-file-access': None
        }
        
        try:
            # Generate PDF
            logger.info(f"Attempting to generate PDF for course: {course.title}")
            if config:
                pdf = pdfkit.from_string(html, False, options=options, configuration=config)
            else:
                pdf = pdfkit.from_string(html, False, options=options)
            
            # Create a BytesIO buffer
            pdf_buffer = BytesIO(pdf)
            pdf_buffer.seek(0)
            
            logger.info(f"PDF successfully generated with size: {len(pdf)} bytes")
        except Exception as pdf_error:
            logger.error(f"PDF generation error: {str(pdf_error)}")
            # Re-raise with more specific message
            raise Exception(f"PDF generation failed: {str(pdf_error)}. Please ensure wkhtmltopdf is properly installed.")
        
        # Generate filename
        filename = f"{course.title.replace(' ', '_')}_Outline.pdf"
        
        # Create a response with the PDF
        response = current_app.response_class(
            pdf_buffer,
            content_type='application/pdf'
        )
        response.headers['Content-Disposition'] = f'attachment; filename="{filename}"'
        
        # Log activity
        activity = Activity(
            user_id=current_user.id,
            action='OUTLINE_DOWNLOAD',  # Using the correct field name 'action' instead of 'action_type'
            details=json.dumps({'course_id': course_id, 'course_type': course_type, 'course_title': course.title})
        )
        db.session.add(activity)
        db.session.commit()
        
        return response
        
    except Exception as e:
        logger.error(f"Error generating PDF: {str(e)}")
        flash(f"Could not generate PDF: {str(e)}", 'error')
        
        # Render fallback HTML view instead
        return render_template('student/outline_fallback.html',
                             course=course,
                             course_outline=course_outline,
                             topics_count=topics_count,
                             course_type=course_type,
                             error_message=str(e))

@student_bp.route('/course/<course_type>/<int:course_id>/lessons')
@login_required
@student_required
def course_lessons(course_type, course_id):
    if course_type == 'short':
        course = ShortCourse.query.get_or_404(course_id)
    elif course_type == 'learn':
        course = LearningCourse.query.get_or_404(course_id)
    else:
        flash('Invalid course type.', 'error')
        return redirect(url_for('student.courses'))
    # Get all topics for this course
    topics = Topic.query.filter_by(course_id=course_id).order_by(Topic.order.asc(), Topic.created_at.asc()).all()
    # Get progress for these topics
    progress_dict = {
        p.topic_id: p for p in Progress.query.filter_by(
            student_id=current_user.id
        ).filter(Progress.topic_id.in_([t.id for t in topics])).all()
    }
    return render_template('student/course_lessons.html', course=course, course_type=course_type, topics=topics, progress=progress_dict)

@student_bp.route('/topic/<topic_id>')
@login_required
@student_required
def topic_view(topic_id):
    # Force query to refresh from database to get latest changes
    db.session.expire_all()  # Clear the entire session to force fresh data
    topic = Topic.query.filter_by(id=topic_id).first_or_404()
    
    print(f"DEBUG - Loading topic {topic_id}: Title={topic.title}, Category={topic.category}, Reading Content Preview: {topic.reading_content[:30] if topic.reading_content else 'None'}")
    
    # Get all topics in this category to check if this topic is accessible
    topics = Topic.query.filter_by(category=topic.category).order_by(Topic.created_at).all()
    
    # Get progress for all topics in the category
    progress_dict = {
        str(p.topic_id): p for p in Progress.query.filter_by(
            student_id=current_user.id
        ).filter(Progress.topic_id.in_([t.id for t in topics])).all()
    }
    
    # Check if topic is accessible
    if not is_topic_accessible(topic, progress_dict, topics):
        flash('Please complete the previous topic first.', 'error')
        # Handle None category case
        category_name = topic.category if topic.category is not None else "General"
        return redirect(url_for('student.category_topics', category_name=category_name))
    
    # Get progress for this topic
    progress = Progress.query.filter_by(
        student_id=current_user.id,
        topic_id=topic_id
    ).first()
    
    # Set topic status
    if progress:
        if progress.speaking_completed and progress.comprehension_completed:
            progress.status = "Completed"
        elif progress.reading_completed or progress.speaking_started or progress.comprehension_started:
            progress.status = "In Progress"
        else:
            progress.status = "Not Started"
            
        # Extract speaking score
        if progress.speaking_scores:
            if isinstance(progress.speaking_scores, dict):
                if 'overall' in progress.speaking_scores:
                    progress.overall_speaking_score = progress.speaking_scores['overall']
                else:
                    found_scores = False
                    for question_id, scores in progress.speaking_scores.items():
                        if isinstance(scores, dict) and 'overall' in scores:
                            progress.overall_speaking_score = scores['overall']
                            found_scores = True
                            break
                    
                    if not found_scores:
                        progress.overall_speaking_score = 0
            else:
                progress.overall_speaking_score = 0
        else:
            progress.overall_speaking_score = 0
    
    # Check if there's a pending vocabulary quiz for this topic
    from app.models.quiz import VocabularyQuiz
    pending_quiz = VocabularyQuiz.query.filter_by(
        student_id=current_user.id,
        topic_id=topic_id,
        completed_at=None
    ).first()
    
    return render_template('student/topic_view.html',
                         topic=topic,
                         progress=progress,
                         pending_quiz=pending_quiz)

@student_bp.route('/topic/<topic_id>/reading')
@login_required
@student_required
def reading_stage(topic_id):
    # Expire all to ensure fresh data
    db.session.expire_all()
    topic = Topic.query.filter_by(id=topic_id).first_or_404()
    
    print(f"DEBUG - Loading reading stage for topic {topic_id}: Title={topic.title}, Reading Content Preview: {topic.reading_content[:30] if topic.reading_content else 'None'}")
    
    # Create or get progress record
    progress = Progress.query.filter_by(
        student_id=current_user.id,
        topic_id=topic_id
    ).first()
    
    if not progress:
        progress = Progress(student_id=current_user.id, topic_id=topic_id)
        db.session.add(progress)
        db.session.commit()
    
    if not progress.reading_started:
        progress.reading_started = datetime.utcnow()
        db.session.commit()
    
    form = CompleteReadingForm()
    return render_template('student/reading_stage.html',
                         topic=topic,
                         progress=progress,
                         form=form)

@student_bp.route('/topic/<topic_id>/reading/complete', methods=['POST'])
@login_required
@student_required
def complete_reading(topic_id):
    form = CompleteReadingForm()
    if not form.validate_on_submit():
        flash('Invalid form submission.', 'error')
        return redirect(url_for('student.reading_stage', topic_id=topic_id))

    progress = Progress.query.filter_by(
        student_id=current_user.id,
        topic_id=topic_id
    ).first_or_404()
    
    progress.reading_completed = datetime.utcnow()
    db.session.commit()
    
    flash('Reading stage completed! Moving on to speaking practice.', 'success')
    return redirect(url_for('student.speaking_stage', topic_id=topic_id))

@student_bp.route('/topic/<topic_id>/comprehension')
@login_required
@student_required
def comprehension_stage(topic_id):
    topic = Topic.query.get_or_404(topic_id)
    progress = Progress.query.filter_by(student_id=current_user.id, topic_id=topic_id).first()
    
    # Check if speaking is completed
    if not progress or not progress.speaking_completed:
        flash('Please complete the speaking stage first.', 'warning')
        return redirect(url_for('student.speaking_stage', topic_id=topic_id))
    
    if not progress.comprehension_started:
        progress.comprehension_started = datetime.utcnow()
        db.session.commit()
    
    # IMPORTANT: Always regenerate questions when loading the comprehension page
    # Delete existing questions for this topic
    if topic.comprehension_questions:
        for question in topic.comprehension_questions:
            db.session.delete(question)
        db.session.commit()
        # Clear the list to ensure we don't use cached questions
        topic.comprehension_questions = []
    
    # Generate new questions using AI
    try:
        # Generate new AI questions using the configured singleton
        from app.services.ai_assessment import get_ai_assessor
        ai = get_ai_assessor()
        questions = ai.generate_comprehension_questions(topic.reading_content)
        
        # Save questions to the topic
        topic.add_comprehension_questions(questions)
        db.session.commit()
        
        logger.info(f"Generated {len(questions)} AI questions for topic {topic_id}")
    except Exception as e:
        logger.error(f"Error generating AI questions for topic {topic_id}: {str(e)}")
        flash('Error generating comprehension questions. Please try again.', 'error')
        return redirect(url_for('student.reading_stage', topic_id=topic_id))
    
    # Get previous submissions
    try:
        submissions = Submission.query.filter_by(progress_id=progress.id).order_by(Submission.created_at.desc()).all()
        previous_submissions = []
        for submission in submissions:
            # Handle both string and native JSON feedback
            if isinstance(submission.feedback_json, str):
                parsed_feedback = safe_json_loads(submission.feedback_json, [])
            else:
                parsed_feedback = submission.feedback_json if submission.feedback_json else []
            
            # Normalize each feedback item into a known structure with numeric fields
            structured_feedback = []
            for item in parsed_feedback:
                if not isinstance(item, dict):
                    continue

                # Helper to coerce numeric-like values
                def nget(d, *keys, default=0):
                    for k in keys:
                        if k in d and d[k] is not None:
                            try:
                                return int(d[k])
                            except Exception:
                                try:
                                    return int(float(d[k]))
                                except Exception:
                                    return default
                    return default

                # Derive values with fallbacks, including nested fields
                grammar_analysis = item.get('grammar_analysis') or {}
                corrections = item.get('corrections') or item.get('grammar_errors') or grammar_analysis.get('errors') or []

                # score may be present or we compute from the three dimension scores
                relevance_score = nget(item, 'relevance_score', 'content_score', default=None)
                if relevance_score is None:
                    relevance_score = nget(item, 'score', default=0)

                sentence_score = nget(item, 'sentence_score', 'coherence_score', default=None)
                if sentence_score is None:
                    sentence_score = nget(item, 'score', default=0)

                grammar_score = nget(item, 'grammar_score', default=None)
                if grammar_score is None:
                    # try nested grammar_analysis.score
                    grammar_score = nget(grammar_analysis, 'score', default=0)

                # Ensure numeric ints
                try:
                    relevance_score = int(relevance_score)
                except Exception:
                    relevance_score = 0
                try:
                    sentence_score = int(sentence_score)
                except Exception:
                    sentence_score = 0
                try:
                    grammar_score = int(grammar_score)
                except Exception:
                    grammar_score = 0

                score = nget(item, 'score', default=None)
                if score is None or score == 0:
                    # fallback to average of dimensions
                    score = round((relevance_score + sentence_score + grammar_score) / 3)
                # include canonical question_score and short_feedback if present
                question_score_val = item.get('question_score') if item.get('question_score') is not None else score
                short_fb = item.get('short_feedback') or item.get('feedback') or item.get('suggestions') or ''
                
                # Get vocabulary and coherence scores with proper fallbacks
                vocabulary_score = nget(item, 'vocabulary_score', default=relevance_score)
                coherence_score = nget(item, 'coherence_score', 'sentence_score', default=sentence_score)
                
                # Get overall_score (prioritize it over regular score)
                overall_score = nget(item, 'overall_score', 'score', default=score)

                structured_feedback.append({
                    'question': item.get('question', 'Unknown Question'),
                    'answer': item.get('answer', 'No Answer'),
                    'score': score,
                    'question_score': int(question_score_val) if question_score_val is not None else int(score),
                    'overall_score': overall_score,
                    'content_score': relevance_score,
                    'grammar_score': grammar_score,
                    'vocabulary_score': vocabulary_score,
                    'coherence_score': coherence_score,
                    'relevance_score': relevance_score,
                    'sentence_score': sentence_score,
                    'grammar_analysis': grammar_analysis,
                    'corrections': corrections,
                    'feedback': item.get('feedback', item.get('content_feedback', 'No feedback available')),
                    'suggestions': item.get('suggestions', item.get('improvements', 'No suggestions available')),
                    'short_feedback': short_fb,
                    'corrected_answer': item.get('corrected_answer', ''),
                    # Additional fields for template
                    'is_relevant': item.get('is_relevant', True),
                    'is_correct': item.get('is_correct', True),
                    'spelling_errors': item.get('spelling_errors', []),
                    'grammar_feedback': item.get('grammar_feedback', ''),
                    'vocabulary_feedback': item.get('vocabulary_feedback', ''),
                    'coherence_feedback': item.get('coherence_feedback', ''),
                    'strengths': item.get('strengths', '')
                })
            
            submission.parsed_feedback = structured_feedback
            previous_submissions.append(submission)
    except Exception as e:
        logger.error(f"Error retrieving submissions: {str(e)}")
        previous_submissions = []
    
    form = ComprehensionForm()
    questions_with_answers = []  # Initialize this variable to prevent errors
    
    return render_template('student/comprehension_stage.html',
                         topic=topic,
                         progress=progress,
                         previous_submissions=previous_submissions,
                         show_latest_feedback=request.args.get('show_feedback', '').lower() == 'true',
                         form=form,
                         questions_with_answers=questions_with_answers)

@student_bp.route('/topic/<topic_id>/comprehension/submit', methods=['POST'])
@login_required
@student_required
def submit_comprehension(topic_id):
    form = ComprehensionForm()
    if not form.validate_on_submit():
        flash('Invalid form submission.', 'error')
        return redirect(url_for('student.comprehension_stage', topic_id=topic_id))

    topic = Topic.query.get_or_404(topic_id)
    progress = Progress.query.filter_by(student_id=current_user.id, topic_id=topic_id).first_or_404()
    
    if not progress.reading_completed:
        flash('Please complete the reading stage first.', 'warning')
        return redirect(url_for('student.reading_stage', topic_id=topic_id))
    
    # Process answers
    answers = {}
    feedback = []
    total_score = 0
    questions_with_answers = []
    
    # Get only the first 3 questions
    questions = topic.comprehension_questions[:3]
    if len(questions) != 3:
        logger.error(f"Expected 3 questions but found {len(questions)} for topic {topic_id}")
        flash('Error with question generation. Please try again.', 'error')
        return redirect(url_for('student.comprehension_stage', topic_id=topic_id))
    
    for question in questions:
        answer_key = f'answer_{question.id}'
        if answer_key not in request.form or not request.form[answer_key].strip():
            flash('Please answer all questions.', 'error')
            return redirect(url_for('student.comprehension_stage', topic_id=topic_id))
        
        questions_with_answers.append({
            'question': question,
            'answer': request.form[answer_key].strip()
        })
        
        student_answer = request.form[answer_key].strip()
        
        # Evaluate answer using AI
        try:
            # Use the new focused comprehension assessor
            from app.services.comprehension_assessor import get_comprehension_assessor
            assessor = get_comprehension_assessor()
            logger.info(f"Starting fast-pass comprehension assessment for question: {question.text}")

            # First compute a fast, local provisional score so the UI gets immediate feedback
            result = assessor.fast_score(
                question_text=question.text,
                student_answer=student_answer,
                reading_content=topic.reading_content
            )

            logger.info(f"Comprehension assessor result keys: {list(result.keys())}")

            # Map new fields into the feedback item while keeping old keys for compatibility
            relevance = result.get('relevance_score', result.get('content_score', 0))
            sentence = result.get('sentence_score', result.get('coherence_score', 0))
            grammar = result.get('grammar_score', 0)
            overall = result.get('overall_score', round((relevance + sentence + grammar) / 3))

            answers[question.id] = student_answer

            # canonical question score (average of components)
            question_score = int(round(overall))

            feedback_item = {
                'question': question.text,
                'answer': student_answer,
                # backward-compatible fields
                'score': overall,
                'question_score': question_score,
                'grammar_score': grammar,
                'content_score': relevance,
                'vocabulary_score': result.get('vocabulary_score', 0),
                'coherence_score': sentence,
                'grammar_analysis': {
                    'score': grammar,
                    'errors': result.get('corrections', [])
                },
                'vocabulary_analysis': result.get('vocabulary_suggestions', result.get('vocabulary_analysis', [])),
                'feedback': result.get('suggestions', ''),
                'grammar_feedback': result.get('grammar_feedback', ''),
                'vocabulary_feedback': result.get('vocabulary_feedback', ''),
                'strengths': result.get('strengths', ''),
                'suggestions': result.get('suggestions', ''),
                'corrected_answer': result.get('corrected_answer', student_answer),
                # new explicit fields
                'relevance_score': relevance,
                'sentence_score': sentence,
                'corrections': result.get('corrections', []),
                # short one-line feedback (prefer fast_score's AI-generated short_feedback if present)
                'short_feedback': result.get('short_feedback', 'Provisional analysis — full AI feedback will appear shortly.'),
                # Mark provisional fast-pass results so the UI can show a badge
                'provisional': bool(result.get('_fast', False))
            }

            logger.info(f"Storing new feedback item grammar: {grammar}, relevance: {relevance}, sentence: {sentence}")

            feedback.append(feedback_item)

            total_score += overall
            
        except Exception as e:
            logger.error(f"Error evaluating answer for question {question.id}: {str(e)}")
            flash('Error evaluating answers. Please try again.', 'error')
            return redirect(url_for('student.comprehension_stage', topic_id=topic_id))
    
    # Calculate average score based on exactly 3 questions (provisional)
    average_score = total_score / 3
    
    # Record submission and log assessment
    try:
        # First try to create the submission object
        try:
            submission = Submission(
                content=json.dumps(answers),
                content_type='comprehension',
                progress_id=progress.id,
                score=average_score,
                feedback=None,
                feedback_json=json.dumps(feedback)
            )
            db.session.add(submission)
        except Exception as e:
            logger.error(f"Error creating submission object: {str(e)}")
            raise

        # Then try to update progress
        try:
            progress.comprehension_completed = datetime.utcnow()
            progress.comprehension_score = average_score
            progress.comprehension_answers = json.dumps(answers)
        except Exception as e:
            logger.error(f"Error updating progress: {str(e)}")
            raise

        # Try to commit the changes
        try:
            db.session.commit()
            # Store the submission id now for background update
            submission_id = submission.id
        except Exception as e:
            logger.error(f"Error committing to database: {str(e)}")
            db.session.rollback()
            raise

        # Log the assessment after successful commit
        try:
            from app.services.ai_assessment import get_ai_assessor
            ai = get_ai_assessor()
            ai.log_assessment(
                student=current_user,
                topic=topic,
                stage='comprehension',
                score=average_score,
                feedback=feedback
            )
        except Exception as e:
            # Log but don't fail if assessment logging fails
            logger.error(f"Error logging assessment (non-critical): {str(e)}")

        # Kick off full AI evaluation in background to replace provisional fast scores
        try:
            import threading
            def _background_full_eval(sub_id, q_with_ans, topic_obj, user_id):
                """Run full assessor.evaluate_answer for each question and update DB record."""
                try:
                    app_assessor = get_comprehension_assessor()
                    full_feedback = []
                    total_full = 0
                    for qa in q_with_ans:
                        q = qa['question']
                        ans = qa['answer']
                        res = app_assessor.evaluate_answer(q.text if hasattr(q, 'text') else q, ans, topic_obj.reading_content)
                        rel = int(res.get('relevance_score', res.get('content_score', 0)))
                        sen = int(res.get('sentence_score', res.get('coherence_score', 0)))
                        gra = int(res.get('grammar_score', 0))
                        ov = int(res.get('overall_score', round((rel + sen + gra) / 3)))

                        # Extract short AI-generated feedback (first sentence of suggestions/feedback)
                        feedback_text = res.get('suggestions') or res.get('feedback') or ''
                        short_fb = ''
                        if feedback_text:
                            try:
                                first_line = [ln for ln in feedback_text.split('\n') if ln.strip()]
                                if first_line:
                                    candidate = first_line[0].strip()
                                else:
                                    candidate = feedback_text.strip()
                                if '.' in candidate:
                                    short_fb = candidate.split('.')[:1][0].strip() + '.'
                                else:
                                    short_fb = candidate[:200]
                            except Exception:
                                short_fb = feedback_text.strip()[:200]

                        full_feedback.append({
                            'question': q.text if hasattr(q, 'text') else q,
                            'answer': ans,
                            'score': ov,
                            'question_score': ov,
                            'grammar_score': gra,
                            'relevance_score': rel,
                            'sentence_score': sen,
                            'corrections': res.get('corrections', []),
                            'feedback': res.get('suggestions', ''),
                            'suggestions': res.get('suggestions', ''),
                            'short_feedback': short_fb,
                            'corrected_answer': res.get('corrected_answer', ans)
                        })
                        total_full += ov

                    avg_full = total_full / max(1, len(full_feedback))

                    # Update submission and progress in DB
                    s = Submission.query.get(sub_id)
                    if s:
                        s.feedback_json = json.dumps(full_feedback)
                        s.score = avg_full
                        db.session.add(s)

                    p = Progress.query.filter_by(id=topic_obj.id, student_id=user_id).first()
                    # NOTE: Progress table has (student_id, topic_id) - find proper progress row
                    p = Progress.query.filter_by(student_id=user_id, topic_id=topic_obj.id).first()
                    if p:
                        p.comprehension_score = avg_full
                        db.session.add(p)

                    db.session.commit()
                    logger.info(f"Background full evaluation completed for submission {sub_id}; avg score {avg_full}")
                except Exception as be:
                    logger.exception(f"Background evaluation failed for submission {sub_id}: {be}")

            # Start background thread
            thread = threading.Thread(target=_background_full_eval, args=(submission_id, questions_with_answers, topic, current_user.id), daemon=True)
            thread.start()
        except Exception as bg_exc:
            logger.error(f"Failed to start background evaluation thread: {bg_exc}")
        
        logger.info(f"Student {current_user.username} completed comprehension for topic {topic_id} with score {average_score}%")
        
        # If score is sufficient, proceed to speaking stage
        if average_score >= 70:
            flash(f'Congratulations! You scored {average_score:.1f}%. Review your results and proceed to the speaking stage when ready.', 'success')
            return redirect(url_for('student.comprehension_stage', topic_id=topic_id, show_feedback=True))
        else:
            flash(f'You scored {average_score:.1f}%. You need 70% to pass. Please review the feedback and try again.', 'warning')
            return redirect(url_for('student.comprehension_stage', topic_id=topic_id, show_feedback=True))
            
    except Exception as e:
        logger.error(f"Error saving submission: {str(e)}", exc_info=True)  # Log full stack trace
        try:
            db.session.rollback()
        except:
            logger.error("Error during rollback", exc_info=True)
            
        error_msg = str(e)
        if 'feedback_json' in error_msg:
            flash('Error processing feedback. Please try again.', 'error')
        elif 'answers' in error_msg:
            flash('Error saving your answers. Please try again.', 'error')
        else:
            flash('Error saving your submission. Please try again.', 'error')
        
        return redirect(url_for('student.comprehension_stage', topic_id=topic_id))

@student_bp.route('/topic/<topic_id>/reading/reset')
@login_required
@student_required
def reset_reading(topic_id):
    """Reset the reading stage for a topic"""
    progress = Progress.query.filter_by(
        student_id=current_user.id,
        topic_id=topic_id
    ).first_or_404()
    
    # Reset reading-related fields while preserving other progress
    from sqlalchemy import null
    progress.reading_completed = null()
    progress.reading_started = None
    progress.reading_time = None
    db.session.commit()
    
    flash('Reading stage has been reset. You can now start again.', 'info')
    return redirect(url_for('student.reading_stage', topic_id=topic_id))

@student_bp.route('/topic/<topic_id>/comprehension/reset')
@login_required
@student_required
def reset_comprehension(topic_id):
    """Reset the comprehension stage for a topic"""
    progress = Progress.query.filter_by(
        student_id=current_user.id,
        topic_id=topic_id
    ).first_or_404()
    
    # Reset comprehension-related fields while preserving other progress
    from sqlalchemy import null
    progress.comprehension_completed = null()
    progress.comprehension_started = None
    progress.comprehension_score = None
    progress.comprehension_answers = None
    db.session.commit()
    
    flash('Comprehension stage has been reset. You can now start again.', 'info')
    return redirect(url_for('student.comprehension_stage', topic_id=topic_id))

@student_bp.route('/topic/<topic_id>/speaking/reset')
@login_required
@student_required
def reset_speaking(topic_id):
    """Reset the speaking practice for a topic"""
    progress = Progress.query.filter_by(
        student_id=current_user.id,
        topic_id=topic_id
    ).first_or_404()
    
    # Reset speaking-related fields while preserving other progress
    from sqlalchemy import null
    progress.speaking_completed = null()
    progress.speaking_started = None
    progress.speaking_scores = None
    progress.speaking_feedback = None
    db.session.commit()
    
    flash('Speaking practice has been reset. You can now start again.', 'info')
    return redirect(url_for('student.speaking_stage', topic_id=topic_id))

@student_bp.route('/topic/<topic_id>/speaking')
@login_required
@student_required
def speaking_stage(topic_id):
    logger.info(f"Student {current_user.id} viewing speaking stage for topic {topic_id}")
    # Get topic
    topic = Topic.query.get_or_404(topic_id)
    
    # IMPORTANT: Always regenerate questions on every page load to ensure fresh content
    # Exception: Don't regenerate if we're viewing a specific question during assessment
    should_regenerate = True
    
    # Check if we're in the middle of a specific question assessment
    if request.args.get('question'):
        # We're viewing a specific question - don't regenerate to maintain consistency
        should_regenerate = False
        logger.info(f"Skipping question regeneration - viewing specific question: {request.args.get('question')}")
    elif request.args.get('view') == 'completed':
        # We're viewing completed results - don't regenerate
        should_regenerate = False
        logger.info("Skipping question regeneration - viewing completed results")
    
    # Force regeneration if explicitly requested via URL parameter
    if request.args.get('regenerate') == '1':
        should_regenerate = True
        logger.info("Force regenerating questions due to 'regenerate=1' parameter")
    
    if should_regenerate:
        logger.info(f"Regenerating fresh speaking questions for topic {topic_id} (reload #{request.args.get('reload', 'N/A')})")
        
        # Delete existing questions for this topic
        if topic.speaking_questions:
            for question in topic.speaking_questions:
                db.session.delete(question)
            db.session.commit()
            # Clear the list to ensure we don't use cached questions
            topic.speaking_questions = []
        
        # Generate speaking questions using AI
        try:
            from app.services.ai_assessment import get_ai_assessor
            ai = get_ai_assessor()  # Get the properly initialized singleton instance
            
            # Log topic details for debugging
            logger.info(f"Topic {topic_id}: '{topic.title}', reading_content length: {len(topic.reading_content or '') if topic.reading_content else 0}")
            if topic.reading_content:
                preview = topic.reading_content[:100] + "..." if len(topic.reading_content) > 100 else topic.reading_content
                logger.debug(f"Reading content preview: {preview}")
            else:
                logger.warning(f"Topic {topic_id} has no reading_content - will use fallback questions")
            
            # Pass the topic object (not just reading_content) so the AI service can handle it properly
            questions = ai.generate_speaking_questions(topic)
            for q in questions:
                topic.speaking_questions.append(q)
            db.session.commit()
            logger.info(f"Generated {len(questions)} AI speaking questions for topic {topic_id}")
            
            # Log the generated questions for debugging
            for i, q in enumerate(questions, 1):
                logger.debug(f"Question {i}: {q.text}")
                
        except Exception as e:
            logger.error(f"Error generating AI speaking questions: {str(e)}")
            flash('Error generating speaking questions. Please try again.', 'error')
            return redirect(url_for('student.topic_view', topic_id=topic_id))
    else:
        logger.info(f"Using existing questions - not regenerating due to: question={request.args.get('question')}, view={request.args.get('view')}")
    
    # Get or create student progress
    progress = Progress.query.filter_by(
        student_id=current_user.id,
        topic_id=topic_id
    ).first() or Progress(
        student_id=current_user.id,
        topic_id=topic_id
    )
    
    if not hasattr(progress, 'speaking_scores') or progress.speaking_scores is None:
        progress.speaking_scores = {}
        db.session.add(progress)
        db.session.commit()
        
    # Use only 2 questions from the topic
    required_question_count = 2
    available_questions = topic.speaking_questions[:required_question_count]
    
    # Debug log showing progress state and completed questions
    completed_question_ids = [q_id for q_id in progress.speaking_scores.keys() if q_id != 'overall']
    completed_set = set(completed_question_ids)
    logger.info(f"Student has completed questions: {', '.join(completed_question_ids)}")
    
    # Check if speaking is already completed (2 questions answered)
    speaking_completed = len(completed_question_ids) >= required_question_count
    
    # Get requested question ID from URL parameter
    requested_question_id = request.args.get('question')
    logger.info(f"Requested question ID: {requested_question_id}")
    
    # If the speaking stage is completed and the user is not requesting a specific question,
    # we can show the completion UI with navigation to the comprehension stage
    if speaking_completed and not requested_question_id and not request.args.get('view') == 'completed':
        # Mark as complete in the progress object if not already marked
        if not progress.speaking_completed:
            progress.speaking_completed = datetime.utcnow()
            db.session.commit()
            
        # Calculate overall score from completed questions
        overall_scores = [s['overall'] for q_id, s in progress.speaking_scores.items() if q_id != 'overall']
        overall_score = sum(overall_scores) / len(overall_scores) if overall_scores else 0
        
        # You could redirect to comprehension stage automatically:
        # return redirect(url_for('student_bp.comprehension_stage', topic_id=topic_id))
        
        # Or show the first question with completion message:
        current_question = available_questions[0]
        logger.info(f"Showing completion message with question: {current_question.text}")
    else:
        # Find the appropriate question to show
        if requested_question_id:
            # If a specific question is requested and it's in the allowed set, show it
            current_question = next(
                (q for q in available_questions if str(q.id) == requested_question_id),
                None
            )
            
            if not current_question:
                # If requested question isn't found in allowed set, show the first available question
                current_question = available_questions[0] if available_questions else None
                logger.warning(f"Requested question {requested_question_id} not found in allowed questions, showing first question")
        else:
            # Show the first unanswered question from the allowed set
            unanswered_questions = [q for q in available_questions if str(q.id) not in completed_set]
            if unanswered_questions:
                current_question = unanswered_questions[0]
                logger.info(f"Showing next unanswered question: {current_question.text}")
            else:
                # All questions completed, show first question for review
                current_question = available_questions[0] if available_questions else None
                logger.info("All questions completed, showing first question for review")
    
    # Question counter handling
    question_count = 2  # Total number of questions
    completed_count = len(completed_question_ids)
    
    # Calculate current question number
    if requested_question_id:
        # If a specific question is requested, find its position in available_questions
        current_question_number = next(
            (i + 1 for i, q in enumerate(available_questions) if str(q.id) == requested_question_id),
            1  # Default to 1 if not found
        )
    else:
        # Show next question number based on completed count
        current_question_number = completed_count + 1
        if current_question_number > question_count:
            current_question_number = question_count
    
    # Set the required number of questions
    required_questions = 2
    
    # Only set the speaking stage as complete if at least 2 questions are answered
    if len(completed_question_ids) >= required_questions:
        all_completed = True
        
        # If speaking_completed is not set, set it now
        if not progress.speaking_completed:
            progress.speaking_completed = datetime.utcnow()
        
        # Always calculate and ensure overall score exists and is valid
        scores = [s.get('overall', 0) for s in progress.speaking_scores.values() 
                if isinstance(s, dict) and s.get('overall') is not None]
        
        # Set a default minimum score of 70% if no scores are available
        if scores:
            progress.speaking_scores['overall'] = sum(scores) / len(scores)
        else:
            # Ensure we have at least some score when speaking is completed
            progress.speaking_scores['overall'] = 70
            
        db.session.commit()
    else:
        all_completed = False
    
    # Calculate correct progress percentage based on required questions
    total_questions = min(len(topic.speaking_questions), required_questions)
    completed_count = min(len(completed_question_ids), required_questions)
    progress_percentage = (completed_count / total_questions * 100) if total_questions > 0 else 0
    
    logger.info(f"Rendering template with question ID: {current_question.id if current_question else 'None'}, " 
                f"completion status: {speaking_completed}, progress: {completed_count}/{question_count}")
    
    return render_template('student/speaking_stage.html', 
                          topic=topic,
                          current_question=current_question,
                          current_question_number=current_question_number,
                          all_completed=speaking_completed,
                          progress_percentage=progress_percentage,
                          completed_count=completed_count,
                          question_count=question_count,
                          total_questions=required_question_count,
                          overall_score=progress.speaking_scores.get('overall', 0))

@student_bp.route('/topic/<topic_id>/speaking/submit', methods=['POST'])
@login_required
@student_required
def submit_speaking(topic_id):
    logger.debug("Starting speaking submission")
    
    try:
        if 'audio' not in request.files:
            logger.error("No audio file in request")
            return jsonify({'error': 'No audio file provided'}), 400
            
        audio_file = request.files['audio']
        if not audio_file:
            logger.error("Empty audio file")
            return jsonify({'error': 'Empty audio file'}), 400

        # Get topic and question
        topic = Topic.query.get_or_404(topic_id)
        question_id = request.form.get('question_id')
        current_question = next(
            (q for q in topic.speaking_questions if str(q.id) == question_id),
            None
        )
        
        if not current_question:
            logger.error(f"Question not found: {question_id}")
            return jsonify({'error': 'Question not found'}), 404

        # Create temp directory
        temp_dir = tempfile.mkdtemp()
        logger.debug(f"Created temp directory: {temp_dir}")

        try:
            # Process audio file
            webm_path = os.path.join(temp_dir, 'input.webm')
            wav_path = os.path.join(temp_dir, 'output.wav')  # Use WAV instead of MP3
            
            # Save audio file with detailed logging
            logger.debug(f"Saving audio file to: {webm_path}")
            audio_file.save(webm_path)
            
            if os.path.exists(webm_path):
                file_size = os.path.getsize(webm_path)
                logger.debug(f"Audio file saved successfully. Size: {file_size} bytes")
                
                # For very small files (likely empty audio), return error
                if file_size < 1000:  # Less than 1KB
                    logger.error("Audio file is too small, likely empty")
                    return jsonify({'error': 'Audio recording is too short or empty'}), 400
            else:
                logger.error(f"Failed to save audio file to {webm_path}")
                return jsonify({'error': 'Failed to save audio file'}), 500
                
            # Skip conversion - let the AIAssessment class handle it
            logger.info("Using AIAssessment to handle audio processing")
            
            # Use the configured AI assessor singleton with proper settings
            from app.services.ai_assessment import get_ai_assessor
            ai = get_ai_assessor()
            logger.debug("Starting AI assessment of audio")
            assessment_result = ai.assess_speaking(
                audio_path=webm_path,  # Send the webm file directly
                question_text=current_question.text,
                topic_content=topic.reading_content
            )

            if not assessment_result:
                logger.error("AI assessment returned None")
                return jsonify({'error': 'AI assessment failed'}), 500
                
            logger.debug(f"AI assessment result: {json.dumps(str(assessment_result)[:200])}...")
            
            # Ensure transcription is not null
            if assessment_result.get('transcription') is None:
                assessment_result['transcription'] = "No transcription available"
            
            # Ensure feedback is properly formatted
            if isinstance(assessment_result.get('feedback'), dict):
                # If it's a dict but missing 'general', add it
                if 'general' not in assessment_result['feedback']:
                    assessment_result['feedback']['general'] = "Assessment completed successfully."
            else:
                # If not a dict, convert to proper structure
                feedback_text = str(assessment_result.get('feedback', "No feedback available"))
                assessment_result['feedback'] = {'general': feedback_text}
            
            # Make sure assessment_result has all required fields
            if 'scores' not in assessment_result:
                assessment_result['scores'] = {
                    'pronunciation': 0,
                    'fluency': 0,
                    'grammar': 0,
                    'relevance': 0,
                    'overall': 0
                }

            # Update progress with speaking scores
            progress = Progress.query.filter_by(
                student_id=current_user.id,
                topic_id=topic_id
            ).first() or Progress(
                student_id=current_user.id,
                topic_id=topic_id
            )
            
            # Initialize speaking_scores if needed
            if not progress.speaking_scores:
                progress.speaking_scores = {}
            
            # Store this question's scores
            progress.speaking_scores[question_id] = assessment_result['scores']
            
            # Mark speaking as complete after 2 questions
            required_questions = 2

            if len(progress.speaking_scores) >= required_questions:
                # Calculate overall score
                question_scores = []
                for qid, scores in progress.speaking_scores.items():
                    if qid != 'overall' and isinstance(scores, dict) and 'overall' in scores:
                        question_scores.append(scores['overall'])
                
                overall_score = 0
                if question_scores:
                    # Calculate average score and store it at the top level
                    overall_score = sum(question_scores) / len(question_scores) 
                    progress.speaking_scores['overall'] = overall_score
                    assessment_result['finalScore'] = overall_score
                else:
                    progress.speaking_scores['overall'] = 0
                    assessment_result['finalScore'] = 0
                
                # Only mark as complete if the score is at least 70%
                if overall_score >= 70:
                    progress.speaking_completed = datetime.utcnow()
                    # Log completion
                    logger.info(f"Student {current_user.id} completed speaking stage for topic {topic_id} with score {progress.speaking_scores['overall']}%")
                else:
                    # Log that the score is too low
                    logger.info(f"Student {current_user.id} completed questions but score {overall_score}% is below 70% threshold")
                    # Set speakingCompleted to false in the result
                    assessment_result['belowThreshold'] = True
                
                assessment_result['hasNextQuestion'] = False
            else:
                # Check if there are more questions available from the first 2 questions
                required_question_count = 2
                available_questions = topic.speaking_questions[:required_question_count]
                completed_question_ids = [qid for qid in progress.speaking_scores.keys() if qid != 'overall']
                unanswered_questions = [q for q in available_questions if str(q.id) not in completed_question_ids]
                
                if unanswered_questions and len(completed_question_ids) < required_question_count:
                    assessment_result['hasNextQuestion'] = True
                    assessment_result['nextQuestionId'] = str(unanswered_questions[0].id)
                    logger.info(f"Next question ID: {unanswered_questions[0].id}")
                else:
                    assessment_result['hasNextQuestion'] = False
            
            db.session.commit()
            logger.debug("Assessment saved successfully")

            return jsonify(assessment_result)

        finally:
            # Cleanup
            try:
                shutil.rmtree(temp_dir)
            except Exception as e:
                logger.error(f"Cleanup error: {str(e)}")

    except Exception as e:
        logger.error(f"Submission error: {str(e)}")
        return jsonify({'error': str(e)}), 500

@student_bp.route('/debug/audio')
@login_required
def debug_audio():
    """Diagnostic page for audio recording and FFmpeg testing"""
    return render_template('student/ffmpeg_debug.html')

@student_bp.route('/debug/api-key', methods=['POST'])
@login_required
def debug_api_key():
    """Debug endpoint to test Gemini API key"""
    try:
        # Get the API key from config
        api_key = current_app.config['GEMINI_API_KEY']
        
        # Check if the API key is the default or empty
        if not api_key or api_key == 'your-gemini-api-key-here':
            return jsonify({
                'api_key_valid': False,
                'error': 'API key is not configured or is set to the default value'
            })
        
        # Try to use the API
        try:
            import google.generativeai as genai
            genai.configure(api_key=api_key)
            
            # Try the primary model first (gemini-1.5-flash)
            model_name = 'gemini-1.5-flash'
            try:
                model = genai.GenerativeModel(model_name)
                response = model.generate_content('Hello, world!')
                return jsonify({
                    'api_key_valid': True,
                    'api_version': 'Gemini 1.5 Flash',
                    'model_used': model_name,
                    'message': 'API key is valid and functioning with the primary model'
                })
            except Exception as primary_error:
                # If primary model fails, try the fallback model (gemini-pro)
                try:
                    model_name = 'gemini-pro' 
                    model = genai.GenerativeModel(model_name)
                    response = model.generate_content('Hello, world!')
                    return jsonify({
                        'api_key_valid': True,
                        'api_version': 'Gemini Pro',
                        'model_used': model_name,
                        'message': 'API key is valid with the fallback model, but your primary model is not available',
                        'primary_model_error': str(primary_error)
                    })
                except Exception as fallback_error:
                    # Both models failed
                    return jsonify({
                        'api_key_valid': False,
                        'error': f'Primary error: {str(primary_error)}. Fallback error: {str(fallback_error)}'
                    })
            
        except Exception as e:
            return jsonify({
                'api_key_valid': False,
                'error': f'Error checking API key: {str(e)}'
            })
            
    except Exception as e:
        return jsonify({
            'api_key_valid': False,
            'error': f'Error checking API key: {str(e)}'
        })

@student_bp.route('/topic/<topic_id>/complete')
@login_required
@student_required
def complete_topic(topic_id):
    """Mark a topic as fully completed and unlock the next topic"""
    topic = Topic.query.get_or_404(topic_id)
    progress = Progress.query.filter_by(
        student_id=current_user.id,
        topic_id=topic_id
    ).first_or_404()
    
    # Ensure all requirements are met
    if not progress.reading_completed:
        flash('You must complete the reading stage first.', 'warning')
        return redirect(url_for('student.reading_stage', topic_id=topic_id))
        
    if not progress.speaking_completed:
        flash('You must complete the speaking stage first.', 'warning')
        return redirect(url_for('student.speaking_stage', topic_id=topic_id))
        
    if not progress.comprehension_completed or progress.comprehension_score < 70:
        flash('You must complete the comprehension stage with at least 70% to complete this topic.', 'warning')
        return redirect(url_for('student.comprehension_stage', topic_id=topic_id))
    
    # Mark topic as fully completed
    if not progress.completed_at:
        progress.completed_at = datetime.utcnow()
        db.session.commit()
        
        # Log activity
        action = Activity.ACTIONS.get('TOPIC_COMPLETED', 'Topic Completed')
        Activity.log(
            action=action,
            user=current_user,
            details=f"Completed topic '{topic.title}' with comprehension score {progress.comprehension_score}%"
        )
        
        flash(f'Congratulations! You have successfully completed "{topic.title}".', 'success')
    
    # Find and unlock next topic in the same category
    next_topic = Topic.query.filter_by(
        category=topic.category
    ).filter(
        Topic.order > topic.order
    ).order_by(
        Topic.order
    ).first()
    
    if next_topic:
        # Create empty progress record for next topic to mark it as accessible
        next_progress = Progress.query.filter_by(
            student_id=current_user.id,
            topic_id=next_topic.id
        ).first()
        
        if not next_progress:
            next_progress = Progress(
                student_id=current_user.id,
                topic_id=next_topic.id
            )
            db.session.add(next_progress)
            db.session.commit()
            
        flash(f'"{next_topic.title}" has been unlocked!', 'success')
        return redirect(url_for('student.topic_view', topic_id=next_topic.id))
    else:
        # Get a safe category name for topic
        category_name = topic.category if topic.category is not None else "General"
        flash('You have completed all topics in this category.', 'success')
        return redirect(url_for('student.category_topics', category_name=category_name))


@student_bp.route('/my-results')
@login_required
@student_required
def my_results():
    """View all submission results with teacher feedback"""
    # Get filter from query params
    status_filter = request.args.get('status', 'all')
    content_type_filter = request.args.get('type', 'all')
    
    # Base query
    query = Submission.query\
        .join(Progress)\
        .join(Topic)\
        .filter(Progress.student_id == current_user.id)
    
    # Apply filters
    if status_filter != 'all':
        query = query.filter(Submission.status == status_filter)
    
    if content_type_filter != 'all':
        query = query.filter(Submission.content_type == content_type_filter)
    
    # Get submissions ordered by date
    submissions = query.order_by(Submission.created_at.desc()).all()
    
    # Parse feedback for each submission
    for submission in submissions:
        try:
            if hasattr(submission, 'feedback_json') and submission.feedback_json:
                if isinstance(submission.feedback_json, str):
                    submission.parsed_feedback = json.loads(submission.feedback_json)
                else:
                    submission.parsed_feedback = submission.feedback_json
            else:
                submission.parsed_feedback = []
        except:
            submission.parsed_feedback = []
    
    # Get counts for filter badges
    total_count = Submission.query.join(Progress).filter(Progress.student_id == current_user.id).count()
    approved_count = Submission.query.join(Progress).filter(Progress.student_id == current_user.id, Submission.status == 'approved').count()
    pending_count = Submission.query.join(Progress).filter(Progress.student_id == current_user.id, Submission.status == 'pending').count()
    needs_revision_count = Submission.query.join(Progress).filter(Progress.student_id == current_user.id, Submission.status == 'needs_revision').count()
    
    return render_template('student/my_results.html',
                         submissions=submissions,
                         status_filter=status_filter,
                         content_type_filter=content_type_filter,
                         total_count=total_count,
                         approved_count=approved_count,
                         pending_count=pending_count,
                         needs_revision_count=needs_revision_count)


@student_bp.route('/result/<int:submission_id>')
@login_required
@student_required
def view_result(submission_id):
    """View detailed result for a specific submission"""
    submission = Submission.query\
        .join(Progress)\
        .filter(
            Progress.student_id == current_user.id,
            Submission.id == submission_id
        ).first_or_404()
    
    # Parse feedback if needed
    try:
        if hasattr(submission, 'feedback_json') and submission.feedback_json:
            if isinstance(submission.feedback_json, str):
                submission.parsed_feedback = json.loads(submission.feedback_json)
            else:
                submission.parsed_feedback = submission.feedback_json
        else:
            submission.parsed_feedback = []
    except:
        submission.parsed_feedback = []
    
    # Parse content if needed
    try:
        if isinstance(submission.content, str):
            submission.parsed_content = json.loads(submission.content)
        else:
            submission.parsed_content = submission.content
    except:
        submission.parsed_content = {}
    
    return render_template('student/result_detail.html',
                         submission=submission,
                         topic=submission.progress.topic)

