<?php
declare(strict_types=1);

/* ===== Акуратне логування (без PII) ===== */
ini_set('display_errors', '0');
error_reporting(E_ALL);
ini_set('log_errors', '1');
ini_set('error_log', '/var/log/php_errors.log');
error_log('update_grades:start');

session_start();
header('Content-Type: application/json; charset=utf-8');

/* ===== Доступ admin|teacher ===== */
if (
    !isset($_SESSION['user_id']) ||
    !in_array($_SESSION['role'] ?? '', ['admin','teacher'], true)
) {
    error_log('update_grades:access_denied');
    echo json_encode(['status' => 'error', 'message' => 'Доступ заборонено']);
    exit;
}

/* ===== DB ===== */
$dbc = '/sites/ej.kpkitp.kiev.ua/includes/db_connect.php';
if (!is_file($dbc)) {
    error_log('update_grades:db_config_missing');
    echo json_encode(['status' => 'error', 'message' => 'Файл підключення до БД не знайдено']);
    exit;
}
require_once $dbc;

if (!isset($conn) || $conn->connect_error) {
    error_log('update_grades:db_connect_error');
    echo json_encode(['status' => 'error', 'message' => 'Помилка підключення до БД']);
    exit;
}
error_log('update_grades:db_connected');

/* ===== Прочитати body як JSON ===== */
function ej_read_json_body(): array {
    $ct = strtolower((string)($_SERVER['CONTENT_TYPE'] ?? $_SERVER['HTTP_CONTENT_TYPE'] ?? ''));
    if (strpos($ct, 'application/json') === false) return [];
    $raw = file_get_contents('php://input');
    if ($raw === false || trim($raw) === '') return [];
    $data = json_decode($raw, true);
    return is_array($data) ? $data : [];
}

/* ===== Уніфікований запит ===== */
$REQ = $_POST;
if (empty($REQ)) {
    $json = ej_read_json_body();
    if (!empty($json)) $REQ = $json;
}

/* ===== Перевірка наявності колонок ===== */
function ej_has_column(mysqli $conn, string $table, string $column): bool {
    $sql = "
        SELECT 1
        FROM INFORMATION_SCHEMA.COLUMNS
        WHERE TABLE_SCHEMA = DATABASE()
          AND TABLE_NAME = ?
          AND COLUMN_NAME = ?
        LIMIT 1
    ";
    $st = $conn->prepare($sql);
    if (!$st) return false;
    $st->bind_param('ss', $table, $column);
    $st->execute();
    $res = $st->get_result();
    $ok = ($res && $res->num_rows > 0);
    $st->close();
    return $ok;
}
function ej_first_existing_column(mysqli $conn, string $table, array $candidates): ?string {
    foreach ($candidates as $col) {
        if (ej_has_column($conn, $table, $col)) return $col;
    }
    return null;
}

/* ===== Вхідні ===== */
$subject_id = isset($REQ['subject_id']) ? (int)$REQ['subject_id'] : 0;
$csrf_post  = (string)($REQ['csrf_token'] ?? '');
$force_recalc = (isset($REQ['force_recalc']) && (string)$REQ['force_recalc'] === '1');

if ($subject_id <= 0) {
    error_log('update_grades:bad_subject_id');
    echo json_encode(['status' => 'error', 'message' => 'Невірний subject_id']);
    exit;
}

/* ===== CSRF ===== */
if (empty($_SESSION['csrf_token']) || !hash_equals((string)$_SESSION['csrf_token'], $csrf_post)) {
    error_log('update_grades:csrf_failed');
    echo json_encode(['status' => 'error', 'message' => 'CSRF помилка']);
    exit;
}

/* ===== Права: teacher може змінювати ТІЛЬКИ свій предмет ===== */
$role = (string)($_SESSION['role'] ?? '');
$user_id = (int)($_SESSION['user_id'] ?? 0);

if ($role === 'teacher') {
    $teacherCol = ej_first_existing_column($conn, 'subjects', ['teacher_id','teacher_user_id','user_id']);
    if ($teacherCol) {
        $sql = "SELECT `$teacherCol` AS teacher_id FROM subjects WHERE id=? LIMIT 1";
        $st = $conn->prepare($sql);
        if ($st) {
            $st->bind_param('i', $subject_id);
            $st->execute();
            $r = $st->get_result();
            $owner = 0;
            if ($r && $row = $r->fetch_assoc()) $owner = (int)($row['teacher_id'] ?? 0);
            $st->close();

            if ($owner > 0 && $owner !== $user_id) {
                error_log('update_grades:teacher_not_owner');
                echo json_encode(['status' => 'error', 'message' => 'Доступ заборонено']);
                exit;
            }
        }
    }
}

/* Якщо немає змін і не примусовий перерахунок — виходимо */
$payload_keys = array_keys($REQ);
$has_grade_pairs = false;
foreach ($payload_keys as $k) {
    if ($k === 'subject_id' || $k === 'csrf_token' || $k === 'force_recalc') continue;
    $has_grade_pairs = true;
    break;
}
if (!$has_grade_pairs && !$force_recalc) {
    echo json_encode([
        'status'      => 'success',
        'added'       => 0,
        'updated'     => 0,
        'unchanged'   => 0,
        'them_recalc' => 0,
        'sem_recalc'  => 0,
        'year_recalc' => 0,
        'message'     => 'Немає змін для збереження'
    ]);
    exit;
}

/* ===== Хелпери для переобчислення ===== */
function ej_parse_flags_topic(string $topic): array {
    $t = ltrim($topic);
    $is_thematic = (bool)preg_match('/^\[(т|Т)\]\s*/u', $t);
    $is_semester = (bool)preg_match('/^\[(с|С)\]\s*/u', $t);
    $is_year     = (bool)preg_match('/^\[(р|Р)\]\s*/u', $t);
    return [$is_thematic ? 1 : 0, $is_semester ? 1 : 0, $is_year ? 1 : 0];
}

function ej_get_lessons(mysqli $conn, int $subject_id): array {
    $rows = [];
    $q = $conn->prepare("SELECT id, lesson_date, topic FROM lessons WHERE subject_id=? ORDER BY lesson_date ASC, id ASC");
    $q->bind_param('i', $subject_id);
    $q->execute();
    $r = $q->get_result();
    while ($x = $r->fetch_assoc()) {
        $x['id'] = (int)$x['id'];
        [$th, $sem, $yr] = ej_parse_flags_topic($x['topic'] ?? '');
        $x['is_thematic'] = $th;
        $x['is_semester'] = $sem;
        $x['is_year']     = $yr;
        $x['is_auto']     = ($th || $sem || $yr) ? 1 : 0;
        $rows[] = $x;
    }
    $q->close();
    return $rows;
}

function ej_get_subject_students(mysqli $conn, int $subject_id): array {
    $gq = $conn->prepare("SELECT group_id FROM subjects WHERE id=? LIMIT 1");
    $gq->bind_param('i', $subject_id);
    $gq->execute();
    $gr = $gq->get_result();
    $group_id = 0;
    if ($row = $gr->fetch_assoc()) $group_id = (int)($row['group_id'] ?? 0);
    $gq->close();
    if ($group_id <= 0) return ['group_id'=>0,'students'=>[]];

    $students = [];
    $st = $conn->prepare("SELECT id FROM users WHERE role='student' AND group_id=?");
    $st->bind_param('i', $group_id);
    $st->execute();
    $rs = $st->get_result();
    while ($u = $rs->fetch_assoc()) $students[] = (int)$u['id'];
    $st->close();

    return ['group_id'=>$group_id,'students'=>$students];
}

/* Завантажити оцінки в карту: grades[lesson_id][student_id] = int(0..12) */
function ej_load_grades_map(mysqli $conn, int $subject_id, array $lesson_ids): array {
    $grades = [];
    $lesson_ids = array_values(array_unique(array_map('intval', $lesson_ids)));
    if (!$lesson_ids) return $grades;

    $in = implode(',', $lesson_ids);
    $sql = "SELECT lesson_id, student_id, grade FROM grades WHERE subject_id=? AND lesson_id IN ($in)";
    $q = $conn->prepare($sql);
    if (!$q) return $grades;
    $q->bind_param('i', $subject_id);
    $q->execute();
    $r = $q->get_result();
    while ($g = $r->fetch_assoc()) {
        $lid = (int)$g['lesson_id'];
        $sid = (int)$g['student_id'];
        $val = is_null($g['grade']) ? 0 : (int)$g['grade'];
        $grades[$lid][$sid] = $val;
    }
    $q->close();
    return $grades;
}

/* Тематична: sum / count(уроків між тематичними), в сегмент НЕ входять авто-уроки. Якщо den=0 або sum=0 -> NULL */
function ej_recompute_all_thematics(mysqli $conn, int $subject_id, array $students, array $lessons, array $grades, int &$recalc_counter): void {
    $thematic_positions = [];
    foreach ($lessons as $idx => $l) {
        if ((int)$l['is_thematic'] === 1) $thematic_positions[] = $idx;
    }
    if (!$thematic_positions) return;

    $upVal = $conn->prepare("
        INSERT INTO grades (subject_id, student_id, lesson_id, grade)
        VALUES (?,?,?,?)
        ON DUPLICATE KEY UPDATE grade=VALUES(grade)
    ");
    $upNull = $conn->prepare("
        INSERT INTO grades (subject_id, student_id, lesson_id, grade)
        VALUES (?,?,?,NULL)
        ON DUPLICATE KEY UPDATE grade=NULL
    ");
    if (!$upVal || !$upNull) return;

    foreach ($thematic_positions as $pos) {
        $prev = -1;
        for ($i = $pos - 1; $i >= 0; $i--) {
            if ((int)$lessons[$i]['is_thematic'] === 1) { $prev = $i; break; }
        }

        $segment = [];
        for ($i = $prev + 1; $i <= $pos - 1; $i++) {
            if ((int)$lessons[$i]['is_auto'] === 0) $segment[] = (int)$lessons[$i]['id'];
        }
        $den = count($segment);
        $them_lesson_id = (int)$lessons[$pos]['id'];

        foreach ($students as $sid) {
            $sum = 0;
            if ($den > 0) {
                foreach ($segment as $lid) $sum += $grades[$lid][$sid] ?? 0;
            }

            if ($den > 0 && $sum > 0) {
                $avg = (int)round($sum / $den);
                if ($avg < 1) $avg = 1;
                if ($avg > 12) $avg = 12;
                $upVal->bind_param('iiii', $subject_id, $sid, $them_lesson_id, $avg);
                $upVal->execute();
            } else {
                $upNull->bind_param('iii', $subject_id, $sid, $them_lesson_id);
                $upNull->execute();
            }
        }

        $recalc_counter++;
    }

    $upVal->close();
    $upNull->close();
}

/* Семестр: середнє по тематичних МІЖ попереднім [С] і поточним [С]. Якщо den=0 або sum=0 -> NULL */
function ej_recompute_all_semesters(mysqli $conn, int $subject_id, array $students, array $lessons, array $grades, int &$recalc_counter): void {
    $upVal = $conn->prepare("
        INSERT INTO grades (subject_id, student_id, lesson_id, grade)
        VALUES (?,?,?,?)
        ON DUPLICATE KEY UPDATE grade=VALUES(grade)
    ");
    $upNull = $conn->prepare("
        INSERT INTO grades (subject_id, student_id, lesson_id, grade)
        VALUES (?,?,?,NULL)
        ON DUPLICATE KEY UPDATE grade=NULL
    ");
    if (!$upVal || !$upNull) return;

    foreach ($lessons as $pos => $l) {
        if ((int)$l['is_semester'] !== 1) continue;

        $prevSem = -1;
        for ($i = $pos - 1; $i >= 0; $i--) {
            if ((int)$lessons[$i]['is_semester'] === 1) { $prevSem = $i; break; }
        }

        $thematic_ids = [];
        for ($i = $prevSem + 1; $i <= $pos - 1; $i++) {
            if ((int)$lessons[$i]['is_thematic'] === 1) $thematic_ids[] = (int)$lessons[$i]['id'];
        }
        $den = count($thematic_ids);
        $sem_lesson_id = (int)$l['id'];

        foreach ($students as $sid) {
            $sum = 0;
            if ($den > 0) {
                foreach ($thematic_ids as $lid) $sum += $grades[$lid][$sid] ?? 0;
            }

            if ($den > 0 && $sum > 0) {
                $avg = (int)round($sum / $den);
                if ($avg < 1) $avg = 1;
                if ($avg > 12) $avg = 12;
                $upVal->bind_param('iiii', $subject_id, $sid, $sem_lesson_id, $avg);
                $upVal->execute();
            } else {
                $upNull->bind_param('iii', $subject_id, $sid, $sem_lesson_id);
                $upNull->execute();
            }
        }

        $recalc_counter++;
    }

    $upVal->close();
    $upNull->close();
}

/* Річна: якщо є 2 семестри -> round((s1+s2)/2), якщо 1 -> s1. Якщо s1=0 -> NULL */
function ej_recompute_all_years(mysqli $conn, int $subject_id, array $students, array $lessons, array $grades, int &$recalc_counter): void {
    $upVal = $conn->prepare("
        INSERT INTO grades (subject_id, student_id, lesson_id, grade)
        VALUES (?,?,?,?)
        ON DUPLICATE KEY UPDATE grade=VALUES(grade)
    ");
    $upNull = $conn->prepare("
        INSERT INTO grades (subject_id, student_id, lesson_id, grade)
        VALUES (?,?,?,NULL)
        ON DUPLICATE KEY UPDATE grade=NULL
    ");
    if (!$upVal || !$upNull) return;

    foreach ($lessons as $pos => $l) {
        if ((int)$l['is_year'] !== 1) continue;

        $semester_ids = [];
        for ($i = 0; $i <= $pos - 1; $i++) {
            if ((int)$lessons[$i]['is_semester'] === 1) $semester_ids[] = (int)$lessons[$i]['id'];
        }
        $year_lesson_id = (int)$l['id'];

        if (!$semester_ids) {
            foreach ($students as $sid) {
                $upNull->bind_param('iii', $subject_id, $sid, $year_lesson_id);
                $upNull->execute();
            }
            $recalc_counter++;
            continue;
        }

        $sem1_id = (int)$semester_ids[0];
        $sem2_id = (int)($semester_ids[1] ?? 0);

        foreach ($students as $sid) {
            $s1 = $grades[$sem1_id][$sid] ?? 0;
            $s2 = ($sem2_id > 0) ? ($grades[$sem2_id][$sid] ?? 0) : 0;

            if ($s1 > 0) {
                if ($sem2_id > 0 && $s2 > 0) $val = (int)round(($s1 + $s2) / 2);
                else $val = (int)$s1;

                if ($val < 1) $val = 1;
                if ($val > 12) $val = 12;

                $upVal->bind_param('iiii', $subject_id, $sid, $year_lesson_id, $val);
                $upVal->execute();
            } else {
                $upNull->bind_param('iii', $subject_id, $sid, $year_lesson_id);
                $upNull->execute();
            }
        }

        $recalc_counter++;
    }

    $upVal->close();
    $upNull->close();
}

/* ===== Підготовка запитів для оцінок ===== */
$sel      = $conn->prepare('SELECT grade FROM grades WHERE subject_id=? AND student_id=? AND lesson_id=?');
$upd_val  = $conn->prepare('UPDATE grades SET grade=? WHERE subject_id=? AND student_id=? AND lesson_id=?');
$upd_null = $conn->prepare('UPDATE grades SET grade=NULL WHERE subject_id=? AND student_id=? AND lesson_id=?');
$ins_val  = $conn->prepare('INSERT INTO grades (subject_id, student_id, lesson_id, grade) VALUES (?,?,?,?)');
$ins_null = $conn->prepare('INSERT INTO grades (subject_id, student_id, lesson_id, grade) VALUES (?,?,?,NULL)');

if (!$sel || !$upd_val || !$upd_null || !$ins_val || !$ins_null) {
    error_log('update_grades:stmt_prepare_error');
    echo json_encode(['status' => 'error', 'message' => 'Помилка підготовки запитів']);
    exit;
}

/* ===== Тіло оновлення ===== */
$added = 0; $updated = 0; $unchanged = 0;
$ok = true;

try {
    /* ===== 1) Оновлення звичайних оцінок (якщо вони є) ===== */
    if ($has_grade_pairs) {
        $conn->begin_transaction();

        // дозволені уроки
        $allowed = [];
        if ($rs = $conn->query('SELECT id FROM lessons WHERE subject_id=' . (int)$subject_id)) {
            while ($row = $rs->fetch_assoc()) {
                $allowed[(int)$row['id']] = true;
            }
        }

        foreach ($REQ as $k => $v) {
            if ($k === 'subject_id' || $k === 'csrf_token' || $k === 'force_recalc') continue;

            if (!preg_match('/^(\d+)_(\d+)$/', (string)$k, $m)) {
                $ok = false;
                error_log('update_grades:bad_key');
                continue;
            }
            $student_id = (int)$m[1];
            $lesson_id  = (int)$m[2];
            if ($student_id <= 0 || $lesson_id <= 0) { $ok = false; error_log('update_grades:bad_ids'); continue; }
            if (!isset($allowed[$lesson_id])) { $ok = false; error_log('update_grades:lesson_not_allowed'); continue; }

            $val = trim((string)$v);
            $grade = null;
            if ($val !== '' && $val !== '-') {
                $g = (int)$val;
                if ($g >= 1 && $g <= 12) { $grade = $g; }
            }

            $sel->bind_param('iii', $subject_id, $student_id, $lesson_id);
            $sel->execute();
            $curRes = $sel->get_result();
            $exists = $curRes && $curRes->num_rows > 0;
            $cur = null;
            if ($exists) {
                $row = $curRes->fetch_assoc();
                $cur = is_null($row['grade']) ? null : (int)$row['grade'];
            }

            if ($exists && $cur === $grade) { $unchanged++; continue; }

            if ($exists) {
                if ($grade === null) {
                    $upd_null->bind_param('iii', $subject_id, $student_id, $lesson_id);
                    if (!$upd_null->execute()) { $ok = false; error_log('update_grades:update_null_error'); }
                    else { $updated++; }
                } else {
                    $upd_val->bind_param('iiii', $grade, $subject_id, $student_id, $lesson_id);
                    if (!$upd_val->execute()) { $ok = false; error_log('update_grades:update_val_error'); }
                    else { $updated++; }
                }
            } else {
                if ($grade === null) {
                    $ins_null->bind_param('iii', $subject_id, $student_id, $lesson_id);
                    if (!$ins_null->execute()) { $ok = false; error_log('update_grades:insert_null_error'); }
                    else { $added++; }
                } else {
                    $ins_val->bind_param('iiii', $subject_id, $student_id, $lesson_id, $grade);
                    if (!$ins_val->execute()) { $ok = false; error_log('update_grades:insert_val_error'); }
                    else { $added++; }
                }
            }
        }

        if ($ok) {
            $conn->commit();
            error_log("update_grades:commit added={$added} updated={$updated} unchanged={$unchanged}");
        } else {
            $conn->rollback();
            error_log('update_grades:rollback');
            echo json_encode(['status' => 'error', 'message' => 'Не вдалося зберегти деякі оцінки']);
            exit;
        }

        /*
         * ВАЖЛИВО (НОВЕ):
         * Більше НЕ відправляємо листи учням/батькам при збереженні.
         * Відправка буде тільки після натискання кнопки "Надіслати оцінки" під уроком.
         */
    }

    /* ===== 2) Переобчислення підсумкових (Т/С/Р) — завжди якщо були зміни або force_recalc ===== */
    $them_recalc = 0;
    $sem_recalc  = 0;
    $year_recalc = 0;

    if ($force_recalc || $added > 0 || $updated > 0) {
        $ctx = ej_get_subject_students($conn, $subject_id);
        $students = $ctx['students'];
        if ($students) {
            $lessons = ej_get_lessons($conn, $subject_id);
            $lesson_ids = array_map(fn($x)=> (int)$x['id'], $lessons);
            $grades_map = ej_load_grades_map($conn, $subject_id, $lesson_ids);

            $conn->begin_transaction();
            try {
                // 1) тематичні
                ej_recompute_all_thematics($conn, $subject_id, $students, $lessons, $grades_map, $them_recalc);

                // оновлюємо карту оцінок (бо семестр/рік читають тематичні/семестрові)
                $grades_map = ej_load_grades_map($conn, $subject_id, $lesson_ids);

                // 2) семестрові
                ej_recompute_all_semesters($conn, $subject_id, $students, $lessons, $grades_map, $sem_recalc);

                // оновлюємо карту оцінок ще раз (бо рік читає семестри)
                $grades_map = ej_load_grades_map($conn, $subject_id, $lesson_ids);

                // 3) річні
                ej_recompute_all_years($conn, $subject_id, $students, $lessons, $grades_map, $year_recalc);

                $conn->commit();

            } catch (Throwable $e) {
                $conn->rollback();
                error_log('update_grades:recalc_tx_failed');
            }

            /*
             * ВАЖЛИВО (НОВЕ):
             * Більше НЕ надсилаємо автоматичні повідомлення/листи класному керівнику при перерахунку.
             * Тепер вся розсилка — тільки вручну з кнопки "Надіслати оцінки".
             */
        }
    }

    echo json_encode([
        'status'      => 'success',
        'added'       => $added,
        'updated'     => $updated,
        'unchanged'   => $unchanged,
        'them_recalc' => $them_recalc,
        'sem_recalc'  => $sem_recalc,
        'year_recalc' => $year_recalc
    ]);

} catch (Throwable $e) {
    error_log('update_grades:exception');
    echo json_encode(['status' => 'error', 'message' => 'Непередбачена помилка при збереженні оцінок']);
} finally {
    if (isset($sel) && $sel) $sel->close();
    if (isset($upd_val) && $upd_val) $upd_val->close();
    if (isset($upd_null) && $upd_null) $upd_null->close();
    if (isset($ins_val) && $ins_val) $ins_val->close();
    if (isset($ins_null) && $ins_null) $ins_null->close();
    error_log('update_grades:end');
}
