<?php
declare(strict_types=1);
/**
 * AgentBrain — conversation history, memory persistence, and result formatting
 * for the RBC AI Agent.
 */

class AgentBrain {

    /**
     * Load recent conversation turns for a thread (oldest first).
     */
    public static function loadHistory(
        string $thread_id,
        int    $user_id,
        int    $limit = 10
    ): array {
        $conn = getDBConnection();
        if (!$conn instanceof mysqli) return [];

        // Order by id, not created_at. Both turns of an exchange are written in
        // the same second, so a created_at sort put them in arbitrary order and
        // the model periodically saw its own reply before the question.
        $stmt = $conn->prepare(
            "SELECT id, role, message, provider_used, created_at
             FROM ai_chat_logs
             WHERE thread_id = ? AND user_id = ?
             ORDER BY id DESC
             LIMIT ?"
        );
        if (!$stmt) return [];
        $stmt->bind_param('sii', $thread_id, $user_id, $limit);
        $stmt->execute();
        $rows = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
        $stmt->close();
        return array_reverse($rows); // chronological order
    }

    /**
     * Turn history rows into a provider-safe messages array.
     *
     * The Anthropic Messages API rejects a conversation that does not begin
     * with a user turn, so a window that happened to start on an assistant row
     * made Claude 400 on every follow-up — the cascade then silently fell
     * through to the next provider. Consecutive same-role turns are merged for
     * the same reason.
     *
     * @param array $history Rows from loadHistory().
     * @param string $next   The new user message to append.
     * @return array<int, array{role: string, content: string}>
     */
    public static function buildMessages(array $history, string $next): array {
        $messages = [];

        foreach ($history as $h) {
            $role    = ($h['role'] ?? '') === 'assistant' ? 'assistant' : 'user';
            $content = trim((string) ($h['message'] ?? ''));
            if ($content === '') continue;               // empty turns are rejected
            if (!$messages && $role !== 'user') continue; // must open on a user turn

            $last = $messages ? count($messages) - 1 : null;
            if ($last !== null && $messages[$last]['role'] === $role) {
                $messages[$last]['content'] .= "\n\n" . $content;
                continue;
            }
            $messages[] = ['role' => $role, 'content' => $content];
        }

        $next = trim($next);
        if ($next !== '') {
            $last = $messages ? count($messages) - 1 : null;
            if ($last !== null && $messages[$last]['role'] === 'user') {
                $messages[$last]['content'] .= "\n\n" . $next;
            } else {
                $messages[] = ['role' => 'user', 'content' => $next];
            }
        }

        return $messages;
    }

    /**
     * Persist one message turn to conversation history.
     */
    public static function saveMessage(
        string  $thread_id,
        int     $user_id,
        string  $role,
        string  $message,
        ?string $provider,
        string  $type    = 'text',
        array   $actions = []
    ): void {
        $conn = getDBConnection();
        if (!$conn instanceof mysqli) return;

        $session_id   = session_id();
        $actions_json = !empty($actions) ? json_encode($actions) : null;

        $stmt = $conn->prepare(
            "INSERT INTO ai_chat_logs
               (session_id, thread_id, user_id, role, message,
                provider_used, message_type, actions_taken)
             VALUES (?,?,?,?,?,?,?,?)"
        );
        if (!$stmt) return;
        $stmt->bind_param(
            'ssisssss',
            $session_id, $thread_id, $user_id,
            $role, $message, $provider, $type, $actions_json
        );
        $stmt->execute();
        $stmt->close();
    }

    /**
     * Upsert a fact into agent memory.
     */
    public static function updateMemory(
        string $key,
        string $value,
        string $type = 'school_fact'
    ): void {
        $conn = getDBConnection();
        if (!$conn instanceof mysqli) return;

        $stmt = $conn->prepare(
            "INSERT INTO ai_agent_memory (memory_key, memory_value, memory_type)
             VALUES (?,?,?)
             ON DUPLICATE KEY UPDATE
               memory_value = VALUES(memory_value),
               memory_type  = VALUES(memory_type),
               updated_at   = NOW()"
        );
        if (!$stmt) return;
        $stmt->bind_param('sss', $key, $value, $type);
        $stmt->execute();
        $stmt->close();
    }

    /**
     * Format an action result into a human-readable one-liner.
     *
     * Every key read here must exist in the corresponding action's return
     * array — see AgentBrain::resultKeyContract() and the test that asserts it.
     * Mismatches are silent: the UI just reports zero while the action worked.
     */
    public static function formatResult(string $type, array $result): string {
        if (!($result['success'] ?? false)) {
            return 'Failed: ' . ($result['error'] ?? $result['message'] ?? 'Unknown error');
        }

        $n = static fn(string $k, $default = 0) => $result[$k] ?? $default;

        return match ($type) {
            'send_fee_reminders'        =>
                'Fee reminders sent to ' . $n('sent_count') . ' of ' . $n('total_found') . ' parents' .
                ($n('failed_count') > 0 ? ', ' . $n('failed_count') . ' could not be reached' : ''),

            'generate_terminal_reports' =>
                'Generated ' . $n('reports_generated') . ' report remarks for ' .
                $n('class_name', '') . ' ' . $n('term', '') .
                ($n('skipped') > 0 ? ', ' . $n('skipped') . ' skipped (no scores)' : ''),

            'generate_exam_paper'       =>
                'Paper created: "' . $n('paper_title', 'Untitled') . '" — ' .
                $n('total_questions') . ' questions (' .
                $n('questions_generated') . ' AI-generated, ' .
                $n('questions_from_bank') . ' from bank)',

            'detect_at_risk_students'   =>
                $n('total_analyzed') . ' students analysed: ' .
                $n('high_risk') . ' high risk, ' . $n('medium_risk') . ' medium risk',

            // The action's real output is a multi-paragraph brief; hand it back
            // rather than the old placeholder, which threw the work away.
            'financial_snapshot'        =>
                (string) ($result['summary_text'] ?? 'Financial analysis complete'),

            'bulk_notify_parents'       =>
                'Notified ' . $n('notified_count') . ' parents' .
                ($n('sms_sent') > 0 ? ', ' . $n('sms_sent') . ' SMS sent' : ''),

            'batch_generate_report_pdfs' =>
                $n('pdf_count') . ' of ' . $n('total_students') . ' report PDF(s) generated' .
                ($n('failed_count') > 0 ? ', ' . $n('failed_count') . ' failed' : ''),

            'create_parent_accounts_bulk' =>
                $n('accounts_created') . ' parent account(s) created, ' .
                $n('already_existed') . ' already existed, ' .
                $n('sms_sent') . ' welcome SMS sent',

            'check_timetable_conflicts' =>
                count((array) $n('conflicts', [])) . ' conflict(s) found, ' .
                count((array) $n('ai_suggestions', [])) . ' suggestion(s), ' .
                $n('empty_slots') . ' empty slot(s)',

            // Was reading sent_count, which this action never returns — so it
            // always reported zero regardless of how many actually went out.
            'send_weekly_feeding_summary' =>
                'Feeding summary sent to ' . $n('notifications_sent') . ' parent(s) for ' .
                $n('week_start', '') . ' to ' . $n('week_end', ''),

            // ── Canteen module (previously fell through to the default) ──
            'bulk_create_canteen_entitlements' =>
                $n('created') . ' entitlement(s) created for ' . $n('class_name', '') .
                ' across ' . count((array) $n('sessions', [])) . ' session(s)' .
                ($n('skipped') > 0 ? ', ' . $n('skipped') . ' skipped' : ''),

            'canteen_daily_report' =>
                (string) ($result['summary'] ?? 'Canteen report ready'),

            'cancel_canteen_session' =>
                'Cancelled ' . $n('session_name', 'session') . ' on ' . $n('session_date', '') .
                ' — ' . $n('cancelled_entitlements') . ' entitlement(s) cancelled, ' .
                $n('sms_sent') . ' parent(s) notified',

            'send_canteen_reminder_sms' =>
                $n('sms_sent') . ' reminder SMS sent' .
                ($n('failed') > 0 ? ', ' . $n('failed') . ' failed' : '') .
                ($n('pending') !== 0 ? ' (' . $n('pending') . ' pending entitlements)' : ''),

            default => 'Completed successfully',
        };
    }

    /**
     * Keys formatResult() depends on, per action type.
     *
     * Kept beside the formatter so a drift between the two is visible in one
     * place, and so tests can assert each action actually returns them.
     *
     * @return array<string, string[]>
     */
    public static function resultKeyContract(): array {
        return [
            'send_fee_reminders'               => ['sent_count', 'failed_count', 'total_found'],
            'generate_terminal_reports'        => ['reports_generated', 'skipped', 'class_name', 'term'],
            'generate_exam_paper'              => ['paper_title', 'total_questions', 'questions_generated', 'questions_from_bank'],
            'detect_at_risk_students'          => ['total_analyzed', 'high_risk', 'medium_risk'],
            'financial_snapshot'               => ['summary_text'],
            'bulk_notify_parents'              => ['notified_count', 'sms_sent'],
            'batch_generate_report_pdfs'       => ['pdf_count', 'total_students'],
            'create_parent_accounts_bulk'      => ['accounts_created', 'already_existed', 'sms_sent'],
            'check_timetable_conflicts'        => ['conflicts', 'ai_suggestions', 'empty_slots'],
            'send_weekly_feeding_summary'      => ['notifications_sent', 'week_start', 'week_end'],
            'bulk_create_canteen_entitlements' => ['created', 'class_name', 'sessions'],
            'canteen_daily_report'             => ['summary'],
            'cancel_canteen_session'           => ['session_name', 'session_date', 'cancelled_entitlements', 'sms_sent'],
            'send_canteen_reminder_sms'        => ['sms_sent'],
        ];
    }
}
