<?php

/**

 * EduTrack Canteen Module — Core Helper Functions

 * Include at the top of every canteen page and AJAX endpoint.

 */



if (!defined('BASE_URL')) {

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

}



// ── License gate ──────────────────────────────────────────────────────────────



/**

 * Returns true when the canteen module is active for this school.

 * Result is cached in session for 1 hour to avoid repeated license lookups.

 */

function canteen_is_active(): bool

{

    if (isset($_SESSION['canteen_active_until']) && time() < $_SESSION['canteen_active_until']) {

        return (bool) $_SESSION['canteen_active'];

    }

    $active = hasModule(CANTEEN_ADDON_KEY);

    $_SESSION['canteen_active']       = $active;

    $_SESSION['canteen_active_until'] = time() + 3600;

    return $active;

}



/**

 * Redirect to canteen upgrade page if module is not active.

 * Call near the top of every canteen/* page.

 */

function canteen_require_active(): void

{

    if (!canteen_is_active()) {

        $upgrade = rtrim(BASE_URL, '/') . '/canteen/upgrade.php';

        header('Location: ' . $upgrade);

        exit;

    }

    // Onboarding: redirect to wizard on first canteen visit (skips AJAX, PIN pages, and onboarding itself)

    $skip_pages = ['onboarding.php', 'upgrade.php', 'scanner_login.php', 'cook_dashboard.php',

                   'hardware_test.php', 'enroll_fingerprint.php', 'enroll_facial.php'];

    $cur = basename($_SERVER['PHP_SELF'] ?? '');

    $is_ajax = !empty($_SERVER['HTTP_X_REQUESTED_WITH'])

        || str_starts_with($cur, 'canteen_')

        || (defined('STDIN'));

    if (!$is_ajax && !in_array($cur, $skip_pages, true)) {

        $settings = canteen_get_settings();

        if (empty($settings['setup_completed'])) {

            header('Location: ' . rtrim(BASE_URL, '/') . '/canteen/onboarding.php');

            exit;

        }

    }

}



// ── Role access ───────────────────────────────────────────────────────────────



/**

 * Abort with 403 if the current user's role is not in $allowed_roles.

 * Pass $is_ajax = true to return JSON instead of redirect.

 */

function canteen_require_role(array $allowed_roles, bool $is_ajax = false): void

{

    $role = $_SESSION['role'] ?? '';

    if (!in_array($role, $allowed_roles, true)) {

        if ($is_ajax) {

            header('Content-Type: application/json', true, 403);

            echo json_encode(['success' => false, 'message' => 'Access denied.']);

            exit;

        }

        header('Location: ' . rtrim(BASE_URL, '/') . '/dashboard.php?error=access_denied');

        exit;

    }

}



// ── Settings ──────────────────────────────────────────────────────────────────



/**

 * Returns canteen_settings row for school_id = 1.

 * Falls back to safe defaults if no row exists yet.

 */

function canteen_get_settings(): array

{

    $conn = getDBConnection();

    $stmt = $conn->prepare('SELECT * FROM canteen_settings WHERE school_id = 1 LIMIT 1');

    $stmt->execute();

    $row = $stmt->get_result()->fetch_assoc();

    $stmt->close();

    if ($row) {

        return $row;

    }

    return [

        'qr_enabled'                 => 1,

        'fingerprint_enabled'        => 0,

        'facial_recognition_enabled' => 0,

        'manual_id_fallback'         => 1,

        'qr_validity_hours'          => 24,

        'auto_ai_report'             => 1,

        'cook_dashboard_refresh_seconds' => 30,

        'facial_min_confidence'      => 0.85,

        'setup_completed'            => 0,

    ];

}



// ── Token helpers ─────────────────────────────────────────────────────────────



function base64url_encode(string $data): string

{

    return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');

}



function base64url_decode(string $data): string|false

{

    return base64_decode(strtr($data, '-_', '+/') . str_repeat('=', (4 - strlen($data) % 4) % 4));

}



/**

 * Generate permanent student identity QR token (Type 1 — fee desk only).

 * Never expires. Signed with IDENTITY| prefix so it cannot be used at the canteen scanner.

 */

function canteen_generate_identity_token(int $student_id): string

{

    return base64url_encode(hash_hmac(

        'sha256',

        'IDENTITY|' . $student_id . '|1',

        CANTEEN_SECRET_KEY,

        true

    ));

}



/**

 * Generate daily session pass QR token (Type 2 — canteen scanner only).

 * Expires end of session day. Signed with SESSION| prefix.

 */

function canteen_generate_session_token(int $student_id, int $session_id): string

{

    return base64url_encode(hash_hmac(

        'sha256',

        'SESSION|' . $student_id . '|' . $session_id . '|' . date('Y-m-d'),

        CANTEEN_SECRET_KEY,

        true

    ));

}



/**

 * Validate a session pass token (Type 2).

 * Returns student_id on success, false on any failure.

 * Uses hash_equals() — constant-time comparison, never == or ===.

 */

function canteen_validate_session_token(string $token, int $session_id): int|false

{

    // Must be a plausible base64url string

    if (!preg_match('/^[A-Za-z0-9_-]{20,}$/', $token)) {

        return false;

    }



    $conn = getDBConnection();



    // Look up the entitlement by qr_token to find the student_id

    $stmt = $conn->prepare(

        'SELECT ce.student_id

         FROM canteen_entitlements ce

         WHERE ce.qr_token = ? AND ce.session_id = ?

         LIMIT 1'

    );

    $stmt->bind_param('si', $token, $session_id);

    $stmt->execute();

    $row = $stmt->get_result()->fetch_assoc();

    $stmt->close();



    if (!$row) {

        return false;

    }



    $student_id = (int) $row['student_id'];



    // Re-derive and compare — reject identity tokens (IDENTITY| prefix) implicitly

    $expected = canteen_generate_session_token($student_id, $session_id);

    if (!hash_equals($expected, $token)) {

        return false;

    }



    return $student_id;

}



/**

 * Validate a student identity token (Type 1).

 * Returns student_id on success, false on any failure.

 * Rejects session pass tokens by HMAC mismatch.

 */

function canteen_validate_identity_token(string $token): int|false

{

    if (!preg_match('/^[A-Za-z0-9_-]{20,}$/', $token)) {

        return false;

    }



    $conn = getDBConnection();



    // Look up which student owns this token

    $stmt = $conn->prepare(

        'SELECT id FROM students WHERE identity_qr_token = ? LIMIT 1'

    );

    $stmt->bind_param('s', $token);

    $stmt->execute();

    $row = $stmt->get_result()->fetch_assoc();

    $stmt->close();



    if (!$row) {

        return false;

    }



    $student_id = (int) $row['id'];



    // Re-derive and compare — rejects session tokens implicitly

    $expected = canteen_generate_identity_token($student_id);

    if (!hash_equals($expected, $token)) {

        return false;

    }



    return $student_id;

}



// ── Rate limiting ─────────────────────────────────────────────────────────────



/**

 * Simple file-based rate limiter.

 * Returns true if the request is allowed, false if the limit is exceeded.

 * $key     — unique bucket identifier (e.g. 'identity_validate_' . $ip)

 * $limit   — max requests allowed in the window

 * $window  — window size in seconds

 */

function canteen_rate_limit(string $key, int $limit, int $window): bool

{

    $safe_key = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $key);

    $file     = sys_get_temp_dir() . '/canteen_rl_' . $safe_key . '.json';



    $now       = microtime(true);

    $cutoff    = $now - $window;

    $timestamps = [];



    if (file_exists($file)) {

        $raw = @file_get_contents($file);

        if ($raw) {

            $timestamps = json_decode($raw, true) ?? [];

        }

    }



    // Drop timestamps outside the window

    $timestamps = array_values(array_filter($timestamps, fn($t) => $t > $cutoff));



    if (count($timestamps) >= $limit) {

        return false;

    }



    $timestamps[] = $now;

    @file_put_contents($file, json_encode($timestamps), LOCK_EX);

    return true;

}



// ── Entitlement generation ────────────────────────────────────────────────────



/**

 * Auto-generate canteen entitlements when a feeding fee is recorded.

 * $weeks_covered > 1 handles bulk/term payments — sessions are queried

 * across the full date window (today through today + N*7 days).

 * Must never throw — any error is logged silently.

 */

function canteen_auto_generate_entitlement(

    int $student_id,

    int $payment_id,

    string $fee_type_name,

    int $weeks_covered = 1

): void {

    try {

        if (!canteen_is_active()) {

            return;

        }

        if (!preg_match('/feeding|canteen|meal/i', $fee_type_name)) {

            return;

        }



        $conn       = getDBConnection();

        $date_from  = date('Y-m-d');

        $date_to    = date('Y-m-d', strtotime("+{$weeks_covered} weeks"));



        // Find all active/scheduled sessions within the covered date window

        $stmt = $conn->prepare(

            "SELECT id, session_date FROM canteen_sessions

             WHERE school_id = 1

               AND session_date BETWEEN ? AND ?

               AND status IN ('scheduled','active')

             ORDER BY session_date ASC, start_time ASC"

        );

        $stmt->bind_param('ss', $date_from, $date_to);

        $stmt->execute();

        $sessions = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);

        $stmt->close();



        $ins = $conn->prepare(

            'INSERT IGNORE INTO canteen_entitlements

             (student_id, session_id, fee_payment_id, qr_token, qr_token_expires_at)

             VALUES (?, ?, ?, ?, ?)'

        );



        foreach ($sessions as $session) {

            $session_id = (int) $session['id'];

            // Token is date-bound — generated with the session's own date so it

            // validates correctly on the day the session runs.

            $session_date = $session['session_date'];

            $qr_token     = base64url_encode(hash_hmac(

                'sha256',

                'SESSION|' . $student_id . '|' . $session_id . '|' . $session_date,

                CANTEEN_SECRET_KEY,

                true

            ));

            $expires_at = $session_date . ' 23:59:59';



            $ins->bind_param('iiiss', $student_id, $session_id, $payment_id, $qr_token, $expires_at);

            $ins->execute();

        }

        $ins->close();

    } catch (Throwable $e) {

        error_log('[Canteen] canteen_auto_generate_entitlement error: ' . $e->getMessage());

    }

}

