<?php
/**
 * حساب المعدلات الفصلية والتراكمية
 * Grade Point Average Calculator
 */

require_once __DIR__ . '/../config/config.php';

class GradeCalculator {
    private $db;
    
    public function __construct() {
        $this->db = Database::getInstance();
    }
    
    /**
     * معالجة الدرجة المدخلة (رقمية أو نصية)
     * 
     * @param string|float $input الدرجة المدخلة
     * @return array ['marks' => float, 'letter_grade' => string, 'points' => float, 'is_text' => bool]
     */
    public function processGradeInput($input) {
        $input = trim($input);
        
        // إذا كانت رقمية، معالجتها كرقم
        if (is_numeric($input)) {
            $marks = (float)$input;
            $grade_info = $this->calculatePoints($marks);
            return [
                'marks' => $marks,
                'letter_grade' => $grade_info['letter'],
                'points' => $grade_info['points'],
                'is_text' => false
            ];
        }
        
        // إذا لم تكن رقمية، معالجتها كنص
        // التحقق من الدرجات النصية المعروفة
        $text_grades = [
            'بد' => ['letter' => 'بد', 'points' => null],
            'ب' => ['letter' => 'بد', 'points' => null],
            'غ' => ['letter' => 'غياب', 'points' => 0.0],
            'غياب' => ['letter' => 'غياب', 'points' => 0.0],
            'غش' => ['letter' => 'غش', 'points' => 0.0],
            'غشش' => ['letter' => 'غش', 'points' => 0.0]
        ];
        
        // إذا كانت من الدرجات المعروفة
        if (isset($text_grades[$input])) {
            $result = $text_grades[$input];
            return [
                'marks' => null, // سيتم حفظ النص الأصلي في marks
                'letter_grade' => $result['letter'],
                'points' => $result['points'],
                'is_text' => true
            ];
        }
        
        // إذا كانت نصاً غير معروف، نسمح به أيضاً
        // نستخدم النص نفسه كـ letter_grade
        return [
            'marks' => null, // سيتم حفظ النص الأصلي في marks
            'letter_grade' => $input, // استخدام النص المدخل كـ letter_grade
            'points' => null, // لا نقاط للنصوص غير المعروفة
            'is_text' => true
        ];
    }
    
    /**
     * تنظيف التقدير الحرفي وإزالة علامات + (مثل B+ -> B, C+ -> C)
     * التقديرات المسموحة فقط: A, B, C, D, F
     */
    public function cleanLetterGrade($letter_grade) {
        if (empty($letter_grade)) {
            return null;
        }
        
        $letter_grade = trim($letter_grade);
        
        // إزالة علامات + من التقديرات
        $letter_grade = str_replace(['+', 'B+', 'C+', 'A+'], ['', 'B', 'C', 'A'], $letter_grade);
        
        // التحقق من أن التقدير صحيح (A, B, C, D, F فقط)
        $valid_grades = ['A', 'B', 'C', 'D', 'F'];
        if (in_array($letter_grade, $valid_grades)) {
            return $letter_grade;
        }
        
        // إذا كان التقدير غير صحيح، حاول استخراج الحرف الأول فقط
        $first_char = strtoupper(substr($letter_grade, 0, 1));
        if (in_array($first_char, $valid_grades)) {
            return $first_char;
        }
        
        // إذا لم يكن تقديراً صحيحاً، أرجعه كما هو (مثل: بد، غياب، إلخ)
        return $letter_grade;
    }
    
    /**
     * حساب النقاط من الدرجة الرقمية
     * التقدير: A (80-100), B (70-79), C (60-69), D (50-59), F (<50)
     * النقاط: A=4, B=3, C=2, D=1, F=0 (مقياس من 4)
     */
    public function calculatePoints($marks) {
        $marks = (float)$marks;
        
        // التحقق بالترتيب من الأعلى للأقل
        if ($marks >= 80 && $marks <= 100) {
            return ['letter' => 'A', 'points' => 4.0];
        } elseif ($marks >= 70 && $marks <= 79) {
            return ['letter' => 'B', 'points' => 3.0];
        } elseif ($marks >= 60 && $marks <= 69) {
            return ['letter' => 'C', 'points' => 2.0];
        } elseif ($marks >= 50 && $marks <= 59) {
            return ['letter' => 'D', 'points' => 1.0];
        } else {
            return ['letter' => 'F', 'points' => 0.0];
        }
    }
    
    /**
     * استخراج النقاط من التقدير الحرفي فقط (عند عدم وجود درجة رقمية أو points محفوظة)
     * A=4, B=3, C=2, D=1, F=0 — غياب/غش=0 — بد لا تدخل (يُرجع null)
     */
    public function pointsFromLetterGrade($letter_grade) {
        if ($letter_grade === null || $letter_grade === '') {
            return null;
        }
        $letter_grade = trim($letter_grade);
        if ($letter_grade === 'بد' || $letter_grade === 'ب') {
            return null; // لا تدخل في المعدل
        }
        $letter = $this->cleanLetterGrade($letter_grade);
        if ($letter === null) {
            return null;
        }
        $map = ['A' => 4.0, 'B' => 3.0, 'C' => 2.0, 'D' => 1.0, 'F' => 0.0];
        if (isset($map[$letter])) {
            return $map[$letter];
        }
        // غياب، غش → 0
        if ($letter_grade === 'غياب' || $letter_grade === 'غ' || $letter_grade === 'غش' || $letter_grade === 'غشش') {
            return 0.0;
        }
        return null;
    }
    
    /**
     * حساب المعدل الفصلي
     * الصيغة: مجموع (الدرجة/20*الساعات المعتمدة) / مجموع الساعات المعتمدة
     * المعدل من 5
     */
    public function calculateSemesterGPA($student_id, $semester_id) {
        // الحصول على جميع الدرجات للطالب في الفصل الدراسي
        $grades = $this->db->fetchAll(
            "SELECT g.*, c.credit_hours, c.is_alternative 
             FROM grades g
             JOIN courses c ON g.course_id = c.id
             WHERE g.student_id = ? AND g.semester_id = ?",
            [$student_id, $semester_id]
        );
        
        if (empty($grades)) {
            return null;
        }
        
        $total_points = 0;
        $total_credit_hours = 0;
        
        foreach ($grades as $grade) {
            // تجاهل مواد البديل في حساب المعدل
            if (isset($grade['is_alternative']) && $grade['is_alternative']) {
                continue;
            }
            
            $marks = $grade['marks'];
            $letter_grade = isset($grade['letter_grade']) ? trim($grade['letter_grade']) : '';
            $credit_hours = (float)$grade['credit_hours'];
            
            // "بد" (أو "ب") لا تدخل في حساب المعدل إطلاقاً
            if ($letter_grade === 'بد' || $letter_grade === 'ب') {
                continue;
            }
            
            // أولاً: إذا كانت marks رقمية، استخدمها مباشرة
            if ($marks !== null && is_numeric($marks)) {
                $marks_value = (float)$marks;
                // الصيغة: (الدرجة/20) * الساعات المعتمدة  → نقاط من 5
                $total_points += ($marks_value / 20.0) * $credit_hours;
                $total_credit_hours += $credit_hours;
                continue;
            }
            
            // ثانياً: إذا لم تكن marks رقمية لكن لدينا points رقمية محفوظة، استخدمها
            if (isset($grade['points']) && $grade['points'] !== null && is_numeric($grade['points'])) {
                $points_value = (float)$grade['points'];
                $total_points += $points_value * $credit_hours;
                $total_credit_hours += $credit_hours;
                continue;
            }
            // ثالثاً: اشتقاق النقاط من التقدير الحرفي (A=4, B=3, C=2, D=1, F=0، غياب/غش=0)
            $points_value = $this->pointsFromLetterGrade($letter_grade);
            if ($points_value !== null) {
                $total_points += $points_value * $credit_hours;
                $total_credit_hours += $credit_hours;
            }
        }
        
        $gpa = $total_credit_hours > 0 ? $total_points / $total_credit_hours : 0;
        
        // حفظ أو تحديث المعدل الفصلي
        $this->db->query(
            "INSERT INTO semester_gpas (student_id, semester_id, total_credit_hours, total_points, gpa)
             VALUES (?, ?, ?, ?, ?)
             ON DUPLICATE KEY UPDATE
             total_credit_hours = VALUES(total_credit_hours),
             total_points = VALUES(total_points),
             gpa = VALUES(gpa),
             calculated_at = CURRENT_TIMESTAMP",
            [$student_id, $semester_id, $total_credit_hours, $total_points, $gpa]
        );
        
        return [
            'gpa' => round($gpa, 2),
            'total_credit_hours' => $total_credit_hours,
            'total_points' => $total_points
        ];
    }
    
    /**
     * حساب المعدل الفصلي من مصفوفة الدرجات (منطق موحد لجميع النتائج)
     * الصيغة: مجموع (الدرجة/20 × الساعات المعتمدة للمادة) / المجموع الكلي للساعات المعتمدة
     * @param array $grades_by_course مصفوفة [course_id => ['marks'=>?, 'letter_grade'=>?, 'points'=>?], ...]
     * @param array $courses_list قائمة المواد مع credit_hours و is_alternative
     * @return array ['gpa' => float, 'total_points' => float, 'total_credit_hours' => float]
     */
    public function calculateSemesterGPAFromGrades(array $grades_by_course, array $courses_list) {
        $total_points = 0.0;
        $total_credit_hours = 0.0;
        foreach ($courses_list as $course) {
            if (isset($course['is_alternative']) && $course['is_alternative']) {
                continue;
            }
            $grade = isset($grades_by_course[$course['id']]) ? $grades_by_course[$course['id']] : null;
            if (!$grade) {
                continue;
            }
            $ch = (float)$course['credit_hours'];
            $letter_grade = isset($grade['letter_grade']) ? trim((string)$grade['letter_grade']) : '';
            if (isset($grade['marks']) && $grade['marks'] !== null && is_numeric($grade['marks'])) {
                $total_points += ((float)$grade['marks'] / 20.0) * $ch;
                $total_credit_hours += $ch;
            } elseif (isset($grade['points']) && $grade['points'] !== null && is_numeric($grade['points'])) {
                $total_points += (float)$grade['points'] * $ch;
                $total_credit_hours += $ch;
            } else {
                $pts = $this->pointsFromLetterGrade($letter_grade);
                if ($pts !== null) {
                    $total_points += $pts * $ch;
                    $total_credit_hours += $ch;
                }
            }
        }
        $gpa = $total_credit_hours > 0 ? round($total_points / $total_credit_hours, 2) : 0;
        return [
            'gpa' => $gpa,
            'total_points' => $total_points,
            'total_credit_hours' => $total_credit_hours
        ];
    }
    
    /**
     * حساب المعدل التراكمي
     * الصيغة: مجموع (الدرجة/20*الساعات المعتمدة) / مجموع الساعات المعتمدة
     * المعدل من 5
     */
    public function calculateCumulativeGPA($student_id) {
        // الحصول على جميع الدرجات للطالب من جميع الفصول
        $grades = $this->db->fetchAll(
            "SELECT g.*, c.credit_hours, c.is_alternative 
             FROM grades g
             JOIN courses c ON g.course_id = c.id
             WHERE g.student_id = ?",
            [$student_id]
        );
        
        if (empty($grades)) {
            return null;
        }
        
        $total_points = 0;
        $total_credit_hours = 0;
        
        foreach ($grades as $grade) {
            // تجاهل مواد البديل في حساب المعدل
            if (isset($grade['is_alternative']) && $grade['is_alternative']) {
                continue;
            }
            
            // تجاهل الدرجات التي ليس لها marks رقمية (مثل "بد", "غياب", "غش")
            $marks = $grade['marks'];
            $letter_grade = isset($grade['letter_grade']) ? trim($grade['letter_grade']) : '';
            
            // التحقق من "بد"
            if ($letter_grade === 'بد' || $letter_grade === 'ب') {
                continue;
            }
            
            $credit_hours = (float)$grade['credit_hours'];
            
            // إذا كانت marks رقمية، استخدمها مباشرة: (الدرجة/20) * الساعات
            if ($marks !== null && is_numeric($marks)) {
                $marks_value = (float)$marks;
                $total_points += ($marks_value / 20.0) * $credit_hours;
                $total_credit_hours += $credit_hours;
                continue;
            }
            // إذا كانت points محفوظة، استخدمها
            if (isset($grade['points']) && $grade['points'] !== null && is_numeric($grade['points'])) {
                $total_points += (float)$grade['points'] * $credit_hours;
                $total_credit_hours += $credit_hours;
                continue;
            }
            // اشتقاق النقاط من التقدير الحرفي
            $points_value = $this->pointsFromLetterGrade($letter_grade);
            if ($points_value !== null) {
                $total_points += $points_value * $credit_hours;
                $total_credit_hours += $credit_hours;
            }
        }
        
        $cgpa = $total_credit_hours > 0 ? $total_points / $total_credit_hours : 0;
        
        // حفظ أو تحديث المعدل التراكمي
        $this->db->query(
            "INSERT INTO cumulative_gpas (student_id, total_credit_hours, total_points, cgpa)
             VALUES (?, ?, ?, ?)
             ON DUPLICATE KEY UPDATE
             total_credit_hours = VALUES(total_credit_hours),
             total_points = VALUES(total_points),
             cgpa = VALUES(cgpa),
             calculated_at = CURRENT_TIMESTAMP",
            [$student_id, $total_credit_hours, $total_points, $cgpa]
        );
        
        return [
            'cgpa' => round($cgpa, 2),
            'total_credit_hours' => $total_credit_hours,
            'total_points' => $total_points
        ];
    }
    
    /**
     * تحديث جميع المعدلات بعد إدخال أو تعديل درجة
     */
    public function updateAllGPAs($student_id) {
        // تحديث المعدل التراكمي
        $this->calculateCumulativeGPA($student_id);
        
        // تحديث جميع المعدلات الفصلية للطالب
        $semesters = $this->db->fetchAll(
            "SELECT DISTINCT semester_id FROM grades WHERE student_id = ?",
            [$student_id]
        );
        
        foreach ($semesters as $semester) {
            $this->calculateSemesterGPA($student_id, $semester['semester_id']);
        }
    }
}

