<?php

declare(strict_types=1);

namespace PpPhp\Modules\Notifications\Controllers;

use PpPhp\Core\Config;
use PpPhp\Core\DB;
use PpPhp\Core\Notification;
use PpPhp\Core\Security;

/**
 * Feeds the notification bell in the PPphp console.
 *
 * Notifications are addressed per-user (or to everyone when `user_id` is NULL),
 * so each signed-in person only ever sees their own.
 */
class NotificationsController
{
    public function __construct(private Config $config) {}

    public function list(): void
    {
        header('Content-Type: application/json');
        Security::RequireAuth();

        $userId = (int) (Security::CurrentUserData()['id'] ?? 0);

        try {
            echo json_encode($this->snapshot($userId));
        } catch (\Throwable $e) {
            // The bell is ambient — never let it break a page.
            echo json_encode(['success' => true, 'unread' => 0, 'items' => []]);
        }
        exit;
    }

    /**
     * Live push channel for the bell (Server-Sent Events).
     *
     * Holds one bounded connection (~25s) and emits the full notification
     * snapshot whenever it changes for this user, then returns so the browser's
     * EventSource reconnects. This retires the constant 30s poll: the bell now
     * updates within ~2.5s of a new notification with a single held request.
     *
     * Two safeguards make this safe on shared hosting:
     *  - session_write_close() up front, so this long-lived request never holds
     *    the session lock (which would serialize/stall every other request from
     *    the same user).
     *  - a hard time cap + tiny sleep slices, so a worker is never pinned for
     *    long and disconnects are noticed promptly. The client falls back to
     *    polling automatically if a proxy buffers the stream.
     */
    public function stream(): void
    {
        Security::RequireAuth();
        $userId = (int) (Security::CurrentUserData()['id'] ?? 0);

        // Release the session lock immediately — critical for a long request.
        if (\function_exists('session_status') && session_status() === PHP_SESSION_ACTIVE) {
            @session_write_close();
        }

        header('Content-Type: text/event-stream; charset=utf-8');
        header('Cache-Control: no-cache, no-transform');
        header('Connection: keep-alive');
        header('X-Accel-Buffering: no'); // stop nginx/proxy buffering the stream

        while (ob_get_level() > 0) {
            @ob_end_flush();
        }
        @set_time_limit(0);
        ignore_user_abort(false); // let PHP end the script when the browser leaves

        echo "retry: 4000\n\n"; // reconnect delay hint if we drop
        @ob_flush();
        @flush();

        try {
            DB::Init();
            DB::SetConnection();
            Notification::SetTableName('notifications');
        } catch (\Throwable $e) {
            echo "event: bye\ndata: {}\n\n";
            @flush();
            exit;
        }

        $lastSig = null;
        $started = time();
        while (!connection_aborted() && (time() - $started) < 25) {
            try {
                $payload = $this->snapshot($userId);
            } catch (\Throwable $e) {
                break; // transient DB issue — close and let the client reconnect
            }
            $topId = (int) ($payload['items'][0]['id'] ?? 0);
            $sig   = $payload['unread'] . ':' . $topId . ':' . count($payload['items']);

            if ($sig !== $lastSig) {
                echo 'data: ' . json_encode($payload) . "\n\n";
                $lastSig = $sig;
            } else {
                echo ": ping\n\n"; // keep-alive comment so proxies don't cut idle streams
            }
            @ob_flush();
            @flush();

            if (connection_aborted()) {
                break;
            }
            usleep(2_500_000); // 2.5s between checks
        }
        exit;
    }

    /**
     * Build the bell payload (unread count + latest items) for one user.
     * Shared by the JSON list endpoint and the SSE stream.
     *
     * @return array{success:bool,unread:int,items:array<int,array<string,mixed>>}
     */
    private function snapshot(int $userId): array
    {
        DB::Init();
        DB::SetConnection();
        Notification::SetTableName('notifications');

        $rows  = Notification::Get($userId, 15);
        $items = array_map(static fn(array $n): array => [
            'id'      => (int) $n['id'],
            'title'   => (string) ($n['title'] ?? ''),
            'message' => (string) ($n['message'] ?? ''),
            'icon'    => (string) ($n['icon'] ?? 'bell'),
            'url'     => (string) ($n['url'] ?? ''),
            'type'    => (string) ($n['type'] ?? 'info'),
            'unread'  => (int) ($n['is_read'] ?? 0) === 0,
            'ts'      => strtotime((string) ($n['created_at'] ?? 'now')) ?: time(),
        ], $rows);

        return [
            'success' => true,
            'unread'  => Notification::Count($userId),
            'items'   => $items,
        ];
    }

    public function markRead(): void
    {
        header('Content-Type: application/json');
        Security::RequireAuth();

        $userId = (int) (Security::CurrentUserData()['id'] ?? 0);
        $input  = json_decode((string) file_get_contents('php://input'), true) ?: [];
        $id     = (int) ($input['id'] ?? 0);

        try {
            DB::Init();
            DB::SetConnection();
            Notification::SetTableName('notifications');

            if ($id > 0) {
                Notification::MarkRead($id);
            } else {
                Notification::MarkAllRead($userId);
            }
            echo json_encode(['success' => true, 'unread' => Notification::Count($userId)]);
        } catch (\Throwable $e) {
            echo json_encode(['success' => false, 'message' => 'Could not update notifications.']);
        }
        exit;
    }
}
