<?php

declare(strict_types=1);

/**

 * RBC AI Agent — Streaming SSE endpoint

 *

 * Accepts: POST JSON { message, thread_id }

 * Emits:   Server-Sent Events (text/event-stream)

 *

 * Event types: thinking · message · clarification · plan ·

 *              action_start · action_result · data · suggestions ·

 *              error · done

 */

if (session_status() === PHP_SESSION_NONE) session_start();

set_time_limit(300); // 5 minutes for AI streaming operations

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

require_once __DIR__ . '/../ai/agent_guard.php';

require_once __DIR__ . '/../ai/class_resolver.php';

require_once __DIR__ . '/../ai/provider_engine.php';

require_once __DIR__ . '/../ai/school_context.php';

require_once __DIR__ . '/../ai/agent_brain.php';



// Action handlers

require_once __DIR__ . '/../ai/actions/send_fee_reminders.php';

require_once __DIR__ . '/../ai/actions/generate_terminal_reports.php';

require_once __DIR__ . '/../ai/actions/generate_exam_paper.php';

require_once __DIR__ . '/../ai/actions/detect_at_risk_students.php';

require_once __DIR__ . '/../ai/actions/financial_snapshot.php';

require_once __DIR__ . '/../ai/actions/send_weekly_feeding_summary.php';

require_once __DIR__ . '/../ai/actions/check_timetable_conflicts.php';

require_once __DIR__ . '/../ai/actions/bulk_notify_parents.php';

require_once __DIR__ . '/../ai/actions/batch_generate_report_pdfs.php';

require_once __DIR__ . '/../ai/actions/create_parent_accounts_bulk.php';



// Canteen Module actions

require_once __DIR__ . '/../ai/actions/bulk_create_canteen_entitlements.php';

require_once __DIR__ . '/../ai/actions/canteen_daily_report.php';

require_once __DIR__ . '/../ai/actions/cancel_canteen_session.php';

require_once __DIR__ . '/../ai/actions/send_canteen_reminder_sms.php';



// ── Parse input ───────────────────────────────────────────────────────────

$input     = json_decode(file_get_contents('php://input'), true) ?? [];

$message   = trim((string) ($input['message']   ?? ''));

$thread_id = trim((string) ($input['thread_id'] ?? session_id()));



// ── Auth: login + role + module licence + CSRF ────────────────────────────

// Previously this checked only that a session existed, so any authenticated

// user could POST here and fire bulk SMS or bulk parent-account creation,

// bypassing both the dashboard role gate and the paid-module licence check.

$user_id = rbcRequireAgentAccess('sse', true, (string) ($input['csrf_token'] ?? ''));



// Release session lock immediately — SSE streams run for a long time and

// holding the session write-lock blocks every other tab from loading pages.

session_write_close();



if ($message === '') {

    http_response_code(400);

    exit('Empty message');

}



// ── SSE headers ───────────────────────────────────────────────────────────

header('Content-Type: text/event-stream');

header('Cache-Control: no-cache, no-store, must-revalidate');

header('X-Accel-Buffering: no');

header('Content-Encoding: none');

header('Access-Control-Allow-Origin: *');



// Clear ALL output buffer levels so SSE data is never gzip-buffered.

// A plain `if` only removes the innermost level; use `while` for all.

@ini_set('zlib.output_compression', '0');

while (ob_get_level()) { ob_end_clean(); }

ob_implicit_flush(true);



// ── SSE helper ────────────────────────────────────────────────────────────

function sseEvent(string $event, mixed $data): void {

    echo "event: {$event}\n";

    echo 'data: ' . json_encode($data, JSON_UNESCAPED_UNICODE) . "\n\n";

    flush();

}



// ── Shutdown guard — catches fatal errors and unexpected deaths ───────────

$_sse_done = false;

register_shutdown_function(function () use (&$_sse_done): void {

    if ($_sse_done) return;

    $err = error_get_last();

    $msg = ($err && in_array($err['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE], true))

        ? 'Server error: ' . $err['message'] . ' in ' . basename($err['file']) . ':' . $err['line']

        : 'The AI agent stopped unexpectedly. Please try again.';

    @sseEvent('error', ['message' => $msg]);

    @sseEvent('done',  ['thread_id' => '']);

});



// ── 1. Thinking indicator ─────────────────────────────────────────────────

sseEvent('thinking', ['message' => 'Understanding your request…']);



// ── 2. Build school context + system prompt ───────────────────────────────

$context    = SchoolContext::build();

$sys_prompt = SchoolContext::buildSystemPrompt($context);



// ── 3. Load conversation history ──────────────────────────────────────────

$history = AgentBrain::loadHistory($thread_id, $user_id, 10);



// ── 4. Assemble messages array ────────────────────────────────────────────

// buildMessages() guarantees the array opens on a user turn and has no

// consecutive same-role turns — Claude 400s on either, which silently

// knocked it out of the cascade on every follow-up message.

$messages = AgentBrain::buildMessages($history, $message);



// ── 5. Call AI ────────────────────────────────────────────────────────────

sseEvent('thinking', ['message' => 'Analysing with live school data…']);



$ai_result = RBCAIEngine::generateWithHistory($messages, $sys_prompt, 2000, true);



if (!$ai_result['success']) {

    // Provider engine returns 'error' key, not 'message'

    $err_msg  = (string) ($ai_result['error'] ?? $ai_result['message'] ?? '');

    $all_fail = (stripos($err_msg, 'no provider') !== false

              || stripos($err_msg, 'all providers') !== false

              || stripos($err_msg, 'not configured') !== false

              || empty($err_msg));

    $detail = $all_fail ? '' : ' (' . substr($err_msg, 0, 120) . ')';

    sseEvent('error', [

        'message' => $all_fail

            ? 'No AI providers are currently available. Please configure at least one API key.'

            : 'AI service temporarily unavailable. Please try again.' . $detail,

        'action'  => $all_fail

            ? 'Check API keys at: Settings → AI Configuration'

            : null,

    ]);

    $_sse_done = true;

    exit;

}



// ── 6. Parse AI JSON response ─────────────────────────────────────────────

$raw    = (string) $ai_result['content'];

$raw    = preg_replace('/^```json\s*/i', '', $raw);

$raw    = preg_replace('/```\s*$/', '', $raw);

$parsed = json_decode(trim($raw), true);



if (!is_array($parsed)) {

    // Non-JSON — treat content as a plain message

    $parsed = [

        'message'              => $ai_result['content'],

        'actions'              => [],

        'follow_up_suggestions'=> [],

    ];

}



// ── 7. Handle clarification ───────────────────────────────────────────────

if (!empty($parsed['needs_clarification'])) {

    $q = (string) ($parsed['clarification_question'] ?? $parsed['message'] ?? '');

    sseEvent('clarification', ['message' => $q]);

    AgentBrain::saveMessage($thread_id, $user_id, 'user',      $message, null);

    AgentBrain::saveMessage($thread_id, $user_id, 'assistant', $q,       $ai_result['provider'], 'clarification');

    $_sse_done = true;

    exit;

}



// ── 8. Stream the text message ────────────────────────────────────────────

sseEvent('message', [

    'content'  => (string) ($parsed['message'] ?? ''),

    'provider' => (string) ($ai_result['provider'] ?? ''),

]);



// ── 9. Multi-step plan card ───────────────────────────────────────────────

if (!empty($parsed['plan_steps']) && is_array($parsed['plan_steps'])) {

    sseEvent('plan', ['steps' => $parsed['plan_steps']]);

}



// ── 10. Execute actions ───────────────────────────────────────────────────

$action_map = [

    'send_fee_reminders'          => 'action_send_fee_reminders',

    'generate_terminal_reports'   => 'action_generate_terminal_reports',

    'generate_exam_paper'         => 'action_generate_exam_paper',

    'detect_at_risk_students'     => 'action_detect_at_risk_students',

    'financial_snapshot'          => 'action_financial_snapshot',

    'send_weekly_feeding_summary' => 'action_send_weekly_feeding_summary',

    'check_timetable_conflicts'   => 'action_check_timetable_conflicts',

    'bulk_notify_parents'         => 'action_bulk_notify_parents',

    'batch_generate_report_pdfs'          => 'action_batch_generate_report_pdfs',

    'create_parent_accounts_bulk'         => 'action_create_parent_accounts_bulk',

    'bulk_create_canteen_entitlements'    => 'action_bulk_create_canteen_entitlements',

    'canteen_daily_report'                => 'action_canteen_daily_report',

    'cancel_canteen_session'              => 'action_cancel_canteen_session',

    'send_canteen_reminder_sms'           => 'action_send_canteen_reminder_sms',

];



$actions_taken = [];

foreach (($parsed['actions'] ?? []) as $action) {

    $type   = (string) ($action['type']        ?? '');

    $params = (array)  ($action['params']       ?? []);

    $desc   = (string) ($action['description']  ?? $type);



    sseEvent('action_start', ['type' => $type, 'description' => $desc]);



    // The model frequently proposes a plausible-but-wrong class name

    // ("JH3" for "JHS 3"). Resolve it before the action queries on it,

    // and fail loudly rather than silently matching zero rows.

    $__cr = rbcResolveClassParam($params, getDBConnection());

    if (!$__cr['ok']) {

        sseEvent('action_result', [

            'type'    => $type,

            'success' => false,

            'result'  => ['success' => false, 'error' => $__cr['error']],

            'summary' => $__cr['error'],

        ]);

        continue;

    }

    $params = $__cr['params'];



    // Money-spending / parent-contacting actions must be confirmed. The

    // system prompt asks the model to confirm; nothing enforced it.

    $__warn = rbcActionNeedsConfirmation($type, $params);

    if ($__warn !== null && empty($action['confirmed'])) {

        sseEvent('confirm_required', [

            'type'    => $type,

            'params'  => $params,

            'message' => $__warn,

        ]);

        continue;

    }



    $fn = $action_map[$type] ?? null;

    if ($fn && function_exists($fn)) {

        $result = $fn($params);

        $summary = AgentBrain::formatResult($type, $result);



        sseEvent('action_result', [

            'type'    => $type,

            'success' => (bool) ($result['success'] ?? false),

            'result'  => $result,

            'summary' => $summary,

        ]);



        // Log to ai_tasks

        $conn = getDBConnection();

        if ($conn instanceof mysqli) {

            $payload  = json_encode($params);

            $res_json = json_encode($result);

            $status   = ($result['success'] ?? false) ? 'completed' : 'failed';

            $prov     = (string) ($ai_result['provider'] ?? '');

            $stmt = $conn->prepare(

                "INSERT INTO ai_tasks

                   (task_type, payload, status, result,

                    triggered_by, provider_used, created_by, started_at, completed_at)

                 VALUES (?,?,?,?,'chat',?,?,NOW(),NOW())"

            );

            if ($stmt) {

                $stmt->bind_param('sssssi', $type, $payload, $status, $res_json, $prov, $user_id);

                $stmt->execute();

                $stmt->close();

            }

        }



        $actions_taken[] = array_merge($action, ['result' => $result]);

    } else {

        sseEvent('action_result', [

            'type'    => $type,

            'success' => false,

            'summary' => "Action '{$type}' is not available.",

        ]);

    }

}



// ── 11. Data-only response ────────────────────────────────────────────────

if (!empty($parsed['data_response'])) {

    sseEvent('data', ['content' => $parsed['data_response']]);

}



// ── 12. Follow-up suggestions ─────────────────────────────────────────────

if (!empty($parsed['follow_up_suggestions']) && is_array($parsed['follow_up_suggestions'])) {

    sseEvent('suggestions', ['suggestions' => $parsed['follow_up_suggestions']]);

}



// ── 13. Update agent memory ───────────────────────────────────────────────

foreach (($parsed['memory_updates'] ?? []) as $mem) {

    $k = (string) ($mem['key']   ?? '');

    $v = (string) ($mem['value'] ?? '');

    $t = (string) ($mem['type']  ?? 'school_fact');

    if ($k !== '' && $v !== '') {

        AgentBrain::updateMemory($k, $v, $t);

    }

}



// ── 14. Save conversation turns ───────────────────────────────────────────

AgentBrain::saveMessage($thread_id, $user_id, 'user',      $message, null, 'text', []);

AgentBrain::saveMessage($thread_id, $user_id, 'assistant',

    (string) ($parsed['message'] ?? ''),

    $ai_result['provider'],

    'text',

    $actions_taken

);



// ── 15. Done ──────────────────────────────────────────────────────────────

$_sse_done = true;

sseEvent('done', ['thread_id' => $thread_id]);

