import sqlite3
import csv
import io
import re
import os
from flask import Flask, render_template, request, redirect, url_for, flash, send_from_directory
import datetime
from typing import Any
from dotenv import load_dotenv

load_dotenv()
# --- App Setup ---
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY') or 'dev-secret-key'
app.config['PHONE_REGEX'] = re.compile(r'^09\d{8}$') # Syrian phone format

# Define the path to the database folder
env_db_file = os.environ.get('DATABASE_FILE')
env_db_folder = os.environ.get('DATABASE_FOLDER')

if env_db_file:
    DATABASE_FILE = env_db_file
    DATABASE_FOLDER = os.path.dirname(DATABASE_FILE) or os.path.join(app.root_path, 'databases')
elif env_db_folder:
    DATABASE_FOLDER = env_db_folder
    DATABASE_FILE = os.path.join(DATABASE_FOLDER, 'students.db')
else:
    DATABASE_FOLDER = os.path.join(app.root_path, 'databases')
    DATABASE_FILE = os.path.join(DATABASE_FOLDER, 'students.db')

# --- Improved Database Functions ---
def get_db_connection():
    db_dir = os.path.dirname(DATABASE_FILE) or DATABASE_FOLDER
    os.makedirs(db_dir, exist_ok=True)
    conn = sqlite3.connect(DATABASE_FILE, timeout=30)  # Increase timeout to 30 seconds
    conn.row_factory = sqlite3.Row
    # Enable WAL mode for better concurrency
    conn.execute('PRAGMA journal_mode=WAL')
    conn.execute('PRAGMA busy_timeout=30000')  # 30 second timeout
    return conn

def init_db():
    os.makedirs(DATABASE_FOLDER, exist_ok=True)
    with get_db_connection() as conn:
        conn.execute('DROP TABLE IF EXISTS students')
        conn.execute('''
            CREATE TABLE students (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                student_name TEXT NOT NULL,
                age INTEGER NOT NULL CHECK(age BETWEEN 5 AND 25),
                parent_name TEXT NOT NULL,
                parent_phone_1 TEXT NOT NULL CHECK(length(parent_phone_1) = 10),
                parent_phone_2 TEXT CHECK(length(parent_phone_2) = 10 OR parent_phone_2 IS NULL),
                student_phone TEXT CHECK(length(student_phone) = 10 OR student_phone IS NULL),
                grade TEXT NOT NULL,
                school_name TEXT NOT NULL,
                address TEXT NOT NULL,
                memorizing TEXT NOT NULL,
                notes TEXT,
                registration_date TEXT NOT NULL,
                points INTEGER DEFAULT 0 NOT NULL
            )
        ''')
        conn.execute('CREATE INDEX idx_student_name ON students(student_name)')
        conn.execute('CREATE INDEX idx_parent_name ON students(parent_name)')

        # NEW: Create lessons table
        conn.execute('''
            CREATE TABLE IF NOT EXISTS lessons (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                lesson_date TEXT NOT NULL,
                created_at TEXT DEFAULT CURRENT_TIMESTAMP
            )
        ''')

        # NEW: Create attendance table
        conn.execute('''
            CREATE TABLE IF NOT EXISTS attendance (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                student_id INTEGER NOT NULL,
                lesson_id INTEGER NOT NULL,
                pages_completed INTEGER DEFAULT 0,
                attended BOOLEAN DEFAULT 1,
                FOREIGN KEY (student_id) REFERENCES students (id) ON DELETE CASCADE,
                FOREIGN KEY (lesson_id) REFERENCES lessons (id) ON DELETE CASCADE,
                UNIQUE(student_id, lesson_id)
            )
        ''')

        # NEW: Create settings table for global variables
        conn.execute('''
            CREATE TABLE IF NOT EXISTS settings (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                key_name TEXT UNIQUE NOT NULL,
                key_value TEXT NOT NULL
            )
        ''')

        # NEW: Initialize total lessons count if not exists
        conn.execute('''
            INSERT OR IGNORE INTO settings (key_name, key_value)
            VALUES ('total_lessons', '0')
        ''')

    print("Database initialized with attendance system")

@app.cli.command('init-db')
def init_db_command():
    init_db()
    print("Database initialized successfully")

# --- Validation Utilities ---
def validate_phone(phone):
    return bool(app.config['PHONE_REGEX'].match(phone)) if phone else True

def validate_student_data(form_data):
    errors = []
    required_fields = ['student_name', 'age', 'parent_name',
                       'parent_phone_1', 'grade', 'school_name',
                       'address', 'memorizing']

    for field in required_fields:
        if not form_data.get(field):
            errors.append(f"حقل '{field}' مطلوب")

    phones = [
        ('parent_phone_1', form_data.get('parent_phone_1')),
        ('parent_phone_2', form_data.get('parent_phone_2')),
        ('student_phone', form_data.get('student_phone'))
    ]

    for field, value in phones:
        if value and not validate_phone(value):
            errors.append(f"رقم الهاتف '{field}' غير صالح. يجب أن يكون 10 أرقام ويبدأ بـ 09")

    try:
        age = int(form_data.get('age', 0))
        if not (5 <= age <= 25):
            errors.append("العمر يجب أن يكون بين 5 و 25 سنة")
    except ValueError:
        errors.append("العمر يجب أن يكون رقماً صحيحاً")

    reg_date_str = form_data.get('registration_date')
    if reg_date_str:
        try:
            datetime.datetime.strptime(reg_date_str, '%Y-%m-%d')
        except ValueError:
            errors.append("تاريخ التسجيل غير صالح. يجب أن يكون بالصيغة YYYY-MM-DD")

    return errors

# --- App Routes ---
@app.route('/')
def index():
    try:
        with get_db_connection() as conn:
            students = conn.execute('''
                SELECT id, student_name, age, parent_name, parent_phone_1, parent_phone_2,
                       student_phone, grade, school_name, address, memorizing, notes,
                       registration_date, points
                FROM students
                ORDER BY student_name ASC
            ''').fetchall()
        return render_template('index.html', students=students)
    except sqlite3.Error as e:
        flash(f'خطأ في قاعدة البيانات: {str(e)}', 'danger')
        return render_template('index.html', students=[])

@app.route('/add_student', methods=['POST'])
def add_student():
    form_data = request.form
    validation_errors = validate_student_data(form_data)

    if validation_errors:
        for error in validation_errors:
            flash(error, 'danger')
        return redirect(url_for('index'))

    registration_date = form_data.get('registration_date')
    if not registration_date:
        registration_date = datetime.date.today().isoformat()

    try:
        student_data = (
            form_data['student_name'],
            int(form_data['age']),
            form_data['parent_name'],
            form_data['parent_phone_1'],
            form_data.get('parent_phone_2') or None,
            form_data.get('student_phone') or None,
            form_data['grade'],
            form_data['school_name'],
            form_data['address'],
            form_data['memorizing'],
            form_data.get('notes') or None,
            registration_date,
        )

        with get_db_connection() as conn:
            conn.execute('''
                INSERT INTO students (
                    student_name, age, parent_name,
                    parent_phone_1, parent_phone_2,
                    student_phone, grade, school_name,
                    address, memorizing, notes, registration_date
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ''', student_data)
            conn.commit()

        flash('تمت إضافة الطالب بنجاح!', 'success')
        return redirect(url_for('index'))

    except sqlite3.IntegrityError as e:
        flash(f'خطأ في قاعدة البيانات: {str(e)}', 'danger')
    except Exception as e:
        flash(f'خطأ غير متوقع: {str(e)}', 'danger')

    return redirect(url_for('index'))

@app.route('/modify_student/<int:student_id>', methods=['GET', 'POST'])
def modify_student(student_id) -> Any :
    if request.method == 'GET':
        try:
            with get_db_connection() as conn:
                student = conn.execute('''
                    SELECT id, student_name, age, parent_name, parent_phone_1, parent_phone_2,
                           student_phone, grade, school_name, address, memorizing, notes,
                           registration_date, points
                    FROM students WHERE id = ?''', (student_id,)).fetchone()

            if student is None:
                flash('الطالب غير موجود.', 'danger')
                return redirect(url_for('index'))

            return render_template('modify_info.html', student=student)
        except sqlite3.Error as e:
            flash(f'خطأ في قاعدة البيانات: {str(e)}', 'danger')
            return redirect(url_for('index'))

    elif request.method == 'POST':
        form_data = request.form
        validation_errors = validate_student_data(form_data)

        if validation_errors:
            for error in validation_errors:
                flash(error, 'danger')
            try:
                with get_db_connection() as conn:
                    student = conn.execute('''
                        SELECT id, student_name, age, parent_name, parent_phone_1, parent_phone_2,
                               student_phone, grade, school_name, address, memorizing, notes,
                               registration_date, points
                        FROM students WHERE id = ?''', (student_id,)).fetchone()
                return render_template('modify_info.html', student=student)
            except sqlite3.Error as e:
                flash(f'خطأ في قاعدة البيانات: {str(e)}', 'danger')
                return redirect(url_for('index'))

        registration_date = form_data.get('registration_date')
        if not registration_date:
            try:
                with get_db_connection() as conn:
                    original_student = conn.execute('SELECT registration_date FROM students WHERE id = ?', (student_id,)).fetchone()
                    if original_student:
                        registration_date = original_student['registration_date']
                    else:
                        registration_date = datetime.date.today().isoformat()
            except sqlite3.Error:
                registration_date = datetime.date.today().isoformat()

        try:
            student_data = (
                form_data['student_name'],
                int(form_data['age']),
                form_data['parent_name'],
                form_data['parent_phone_1'],
                form_data.get('parent_phone_2') or None,
                form_data.get('student_phone') or None,
                form_data['grade'],
                form_data['school_name'],
                form_data['address'],
                form_data['memorizing'],
                form_data.get('notes') or None,
                registration_date,
                student_id
            )

            with get_db_connection() as conn:
                conn.execute('''
                    UPDATE students SET
                        student_name = ?,
                        age = ?,
                        parent_name = ?,
                        parent_phone_1 = ?,
                        parent_phone_2 = ?,
                        student_phone = ?,
                        grade = ?,
                        school_name = ?,
                        address = ?,
                        memorizing = ?,
                        notes = ?,
                        registration_date = ?
                    WHERE id = ?
                ''', student_data)
                conn.commit()

            flash('تم تحديث بيانات الطالب بنجاح!', 'success')
            return redirect(url_for('index'))

        except sqlite3.IntegrityError as e:
            flash(f'خطأ في قاعدة البيانات: {str(e)}', 'danger')
        except Exception as e:
            flash(f'خطأ غير متوقع: {str(e)}', 'danger')

        return redirect(url_for('index'))


@app.route('/import_csv', methods=['POST'])
def import_csv():
    if 'file' not in request.files:
        flash('لم يتم تقديم ملف', 'danger')
        return redirect(url_for('index'))

    file = request.files['file']
    if file.filename == '':
        flash('لم يتم اختيار ملف', 'warning')
        return redirect(url_for('index'))

    if not str(file.filename).lower().endswith('.csv'):
        flash('صيغة الملف غير مدعومة. يجب أن يكون CSV', 'danger')
        return redirect(url_for('index'))

    try:
        stream = io.TextIOWrapper(file.stream, encoding='utf-8-sig')
        csv_reader = csv.reader(stream)
        valid_rows = []
        row_errors = []

        expected_columns = 12

        for i, row in enumerate(csv_reader, 1):
            if len(row) != expected_columns:
                row_errors.append(f'السطر {i}: عدد الأعمدة غير صحيح ({expected_columns} مطلوبة)')
                continue

            student_data = {
                'student_name': row[0].strip(),
                'age': row[1].strip(),
                'parent_name': row[2].strip(),
                'parent_phone_1': row[3].strip(),
                'parent_phone_2': row[4].strip(),
                'student_phone': row[5].strip(),
                'grade': row[6].strip(),
                'school_name': row[7].strip(),
                'address': row[8].strip(),
                'memorizing': row[9].strip(),
                'notes': row[10].strip(),
                'registration_date': row[11].strip()
            }

            if not student_data['registration_date']:
                student_data['registration_date'] = datetime.date.today().isoformat()

            errors = validate_student_data(student_data)
            if errors:
                row_errors.append(f'السطر {i}: {"; ".join(errors)}')
                continue

            valid_rows.append((
                student_data['student_name'],
                int(student_data['age']),
                student_data['parent_name'],
                student_data['parent_phone_1'],
                student_data['parent_phone_2'] or None,
                student_data['student_phone'] or None,
                student_data['grade'],
                student_data['school_name'],
                student_data['address'],
                student_data['memorizing'],
                student_data['notes'] or None,
                student_data['registration_date']
            ))

        if row_errors:
            flash(f'تم العثور على أخطاء في {len(row_errors)} سطراً', 'warning')
            for error in row_errors[:5]:
                flash(error, 'danger')
            if len(row_errors) > 5:
                flash(f'...و {len(row_errors)-5} أخطاء إضافية', 'danger')

        if valid_rows:
            with get_db_connection() as conn:
                conn.executemany('''
                    INSERT INTO students (
                        student_name, age, parent_name,
                        parent_phone_1, parent_phone_2,
                        student_phone, grade, school_name,
                        address, memorizing, notes, registration_date
                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                ''', valid_rows)
                conn.commit()
            flash(f'تم استيراد {len(valid_rows)} طالب بنجاح', 'success')
        else:
            flash('لم يتم استيراد أي سجلات', 'warning')

    except csv.Error as e:
        flash(f'خطأ في معالشة CSV: {str(e)}', 'danger')
    except Exception as e:
        flash(f'خطأ غير متوقع: {str(e)}', 'danger')

    return redirect(url_for('index'))

@app.route('/download_csv_template')
def download_csv_template():
    return send_from_directory('templates', 'template.csv', as_attachment=True)

@app.route('/points', methods=['GET', 'POST'])
def points():
    if request.method == 'POST':
        # Now student_id will be a list of selected IDs from the checkboxes
        selected_student_ids = request.form.getlist('student_id')
        point_amount_str = request.form.get('point_amount')
        operation = request.form.get('operation')

        if not selected_student_ids or not point_amount_str or not operation:
            flash('الرجاء اختيار طالب واحد على الأقل وتعبئة جميع الحقول المطلوبة.', 'danger')
            return redirect(url_for('points'))

        try:
            point_amount = int(point_amount_str)

            if point_amount <= 0:
                flash('الرجاء إدخال قيمة نقاط أكبر من صفر.', 'danger')
                return redirect(url_for('points'))

            with get_db_connection() as conn:
                # Ensure IDs are integers and unique
                int_selected_ids = sorted(list(set([int(sid) for sid in selected_student_ids if sid.isdigit()])))
                if not int_selected_ids:
                    flash('لم يتم تحديد أي طالب صالح.', 'danger')
                    return redirect(url_for('points'))

                placeholders = ','.join(['?'] * len(int_selected_ids))
                query = f"SELECT id, student_name, points FROM students WHERE id IN ({placeholders})"
                students_to_update = conn.execute(query, int_selected_ids).fetchall()

                if not students_to_update:
                    flash('لم يتم العثور على أي طلاب مطابقين للاختيار.', 'danger')
                    return redirect(url_for('points'))

                updated_details = []
                for student in students_to_update:
                    current_points = student['points']
                    student_name = student['student_name']
                    new_points = current_points

                    if operation == 'add':
                        new_points += point_amount
                    elif operation == 'remove':
                        if current_points < point_amount:
                            new_points = 0 # Cap at zero
                        else:
                            new_points -= point_amount
                    else:
                        flash('عملية غير صالحة.', 'danger')
                        return redirect(url_for('points'))

                    conn.execute('UPDATE students SET points = ? WHERE id = ?', (new_points, student['id']))
                    updated_details.append(f"{student_name} (أصبح {new_points})")

                conn.commit()

                flash_op_text = "إضافة" if operation == "add" else "خصم"
                if len(updated_details) == 1:
                    flash(f'تمت عملية {flash_op_text} النقاط للطالب {updated_details[0].replace(" (أصبح", " والنقاط الجديدة")}.', 'success')
                else:
                    flash_message_head = f'تمت عملية {flash_op_text} النقاط لـ {len(updated_details)} طلاب.'
                    flash_message_body = 'التفاصيل: ' + ', '.join(updated_details[:5])
                    if len(updated_details) > 5:
                        flash_message_body += f'... والمزيد.'
                    flash(f'{flash_message_head} {flash_message_body}', 'success')

        except ValueError:
            flash('النقاط يجب أن تكون أرقاماً صحيحة.', 'danger')
        except sqlite3.Error as e:
            flash(f'خطأ في قاعدة البيانات: {str(e)}', 'danger')
        except Exception as e:
            flash(f'خطأ غير متوقع: {str(e)}', 'danger')

        return redirect(url_for('points'))

    else: # GET request
        try:
            with get_db_connection() as conn:
                students = conn.execute('SELECT id, student_name, points FROM students ORDER BY student_name ASC').fetchall()
            return render_template('points.html', students=students)
        except sqlite3.Error as e:
            flash(f'خطأ في قاعدة البيانات: {str(e)}', 'danger')
            return render_template('points.html', students=[])

@app.route('/delete_student/<int:student_id>', methods=['POST'])
def delete_student(student_id):
    try:
        with get_db_connection() as conn:
            student = conn.execute('SELECT id FROM students WHERE id = ?', (student_id,)).fetchone()
            if student is None:
                flash('الطالب غير موجود.', 'danger')
                return redirect(url_for('index'))

            conn.execute('DELETE FROM students WHERE id = ?', (student_id,))
            conn.commit()
            flash('تم حذف الطالب بنجاح!', 'success')
    except sqlite3.Error as e:
        flash(f'خطأ في قاعدة البيانات أثناء الحذف: {str(e)}', 'danger')
    except Exception as e:
        flash(f'خطأ غير متوقع أثناء الحذف: {str(e)}', 'danger')

    return redirect(url_for('index'))

@app.route('/record', methods=['GET', 'POST'])
def record():
    today = datetime.date.today().isoformat()

    if request.method == 'POST':
        # Handle form submission for attendance and pages
        conn = None
        try:
            lesson_date = request.form.get('lesson_date')
            if not lesson_date:
                lesson_date = today

            conn = get_db_connection()

            # Start a transaction explicitly
            conn.execute('BEGIN IMMEDIATE TRANSACTION')

            # Create new lesson
            lesson_result = conn.execute(
                'INSERT INTO lessons (lesson_date) VALUES (?) RETURNING id',
                (lesson_date,)
            ).fetchone()
            lesson_id = lesson_result['id']

            # Process attendance for each student
            student_ids = request.form.getlist('student_id')
            attended_students = request.form.getlist('attended')
            pages_data = request.form.getlist('pages_completed')

            # Use executemany for better performance
            attendance_data = []
            for i, student_id in enumerate(student_ids):
                attended = 1 if student_id in attended_students else 0
                pages = pages_data[i] if i < len(pages_data) else '0'

                try:
                    pages_int = int(pages) if pages.strip() else 0
                except ValueError:
                    pages_int = 0

                attendance_data.append((student_id, lesson_id, pages_int, attended))

            if attendance_data:
                conn.executemany('''
                    INSERT OR REPLACE INTO attendance (student_id, lesson_id, pages_completed, attended)
                    VALUES (?, ?, ?, ?)
                ''', attendance_data)

            # Update total lessons count
            current_total = get_total_lessons(conn)
            update_total_lessons(conn, current_total + 1)

            # Commit the transaction
            conn.commit()
            flash('تم حفظ بيانات الحضور والإنجاز بنجاح!', 'success')

        except sqlite3.OperationalError as e:
            if conn:
                conn.rollback()
            if 'locked' in str(e):
                flash('قاعدة البيانات مشغولة حالياً. الرجاء المحاولة مرة أخرى بعد بضع ثوانٍ.', 'danger')
            else:
                flash(f'خطأ في قاعدة البيانات: {str(e)}', 'danger')
        except Exception as e:
            if conn:
                conn.rollback()
            flash(f'خطأ في حفظ البيانات: {str(e)}', 'danger')
        finally:
            if conn:
                conn.close()

        return redirect(url_for('record'))

    else:  # GET request
        students_data = []
        conn = None

        try:
            conn = get_db_connection()
            students_data = get_students_with_attendance(conn)

        except Exception as e:
            flash(f'خطأ في تحميل البيانات: {str(e)}', 'danger')
            students_data = []
        finally:
            if conn:
                conn.close()

        return render_template('record.html', students=students_data, today=today)

# Helper functions that accept connection as parameter
def get_total_lessons(conn=None):
    close_conn = False
    if conn is None:
        conn = get_db_connection()
        close_conn = True

    try:
        result = conn.execute("SELECT key_value FROM settings WHERE key_name = 'total_lessons'").fetchone()
        return int(result['key_value']) if result else 0
    finally:
        if close_conn:
            conn.close()

def update_total_lessons(conn, new_total):
    conn.execute("UPDATE settings SET key_value = ? WHERE key_name = 'total_lessons'", (str(new_total),))

def get_students_with_attendance(conn):
    """Get students with their attendance statistics"""
    students = conn.execute('''
        SELECT s.id, s.student_name, s.points,
               COUNT(a.id) as lessons_attended,
               COALESCE(SUM(a.pages_completed), 0) as total_pages,
               (SELECT key_value FROM settings WHERE key_name = 'total_lessons') as total_lessons
        FROM students s
        LEFT JOIN attendance a ON s.id = a.student_id AND a.attended = 1
        GROUP BY s.id, s.student_name, s.points
        ORDER BY s.student_name ASC
    ''').fetchall()

    students_data = []
    for student in students:
        total_lessons = int(student['total_lessons']) if student['total_lessons'] else 0
        attendance_percentage = (student['lessons_attended'] / total_lessons * 100) if total_lessons > 0 else 0

        students_data.append({
            'id': student['id'],
            'student_name': student['student_name'],
            'points': student['points'],
            'lessons_attended': student['lessons_attended'],
            'total_pages': student['total_pages'],
            'attendance_percentage': round(attendance_percentage, 1),
            'total_lessons': total_lessons
        })

    return students_data

# NEW: Route to get attendance history for a student
@app.route('/student_attendance/<int:student_id>')
def student_attendance(student_id):
    try:
        with get_db_connection() as conn:
            attendance_history = conn.execute('''
                SELECT l.lesson_date, a.pages_completed, a.attended
                FROM attendance a
                JOIN lessons l ON a.lesson_id = l.id
                WHERE a.student_id = ?
                ORDER BY l.lesson_date DESC
                LIMIT 20
            ''', (student_id,)).fetchall()

            student = conn.execute('SELECT student_name FROM students WHERE id = ?', (student_id,)).fetchone()

            return render_template('student_attendance.html',
                                 attendance_history=attendance_history,
                                 student=student)
    except Exception as e:
        flash(f'خطأ في تحميل سجل الحضور: {str(e)}', 'danger')
        return redirect(url_for('record'))

if __name__ == '__main__':
    app.run()
