<?php

declare(strict_types=1);

namespace App\Controller\Admin;

use App\Service\TagRebuildStatusProvider;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\Routing\Attribute\Route;

final class TagRebuildStreamController
{
    private const POLL_INTERVAL_SECONDS = 2;
    private const KEEPALIVE_INTERVAL_SECONDS = 10;

    #[Route('/admin/tags/rebuild/stream', name: 'admin_tags_rebuild_stream', methods: ['GET'])]
    public function stream(TagRebuildStatusProvider $provider): StreamedResponse
    {
        $response = new StreamedResponse(function () use ($provider): void {
            self::disableOutputBuffering();

            echo "retry: 3000\n";
            self::sendEvent('ping', ['init' => true]);

            $lastPayloadHash = null;
            $lastKeepaliveAt = time();

            while (!connection_aborted()) {
                $data = $provider->getLatestStatus();

                if ($data !== null) {
                    $payloadHash = md5(
                        json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?: 'null'
                    );

                    if ($payloadHash !== $lastPayloadHash) {
                        self::sendEvent('message', $data);
                        $lastPayloadHash = $payloadHash;
                        $lastKeepaliveAt = time();
                    }
                }

                if ((time() - $lastKeepaliveAt) >= self::KEEPALIVE_INTERVAL_SECONDS) {
                    self::sendEvent('ping', [
                        'ts' => (new \DateTimeImmutable())->format(DATE_ATOM),
                    ]);
                    $lastKeepaliveAt = time();
                }

                sleep(self::POLL_INTERVAL_SECONDS);
            }
        });

        $response->headers->set('Content-Type', 'text/event-stream');
        $response->headers->set('Cache-Control', 'no-cache, no-store, must-revalidate');
        $response->headers->set('Pragma', 'no-cache');
        $response->headers->set('Expires', '0');
        $response->headers->set('Connection', 'keep-alive');
        $response->headers->set('X-Accel-Buffering', 'no');

        return $response;
    }

    private static function disableOutputBuffering(): void
    {
        while (ob_get_level() > 0) {
            @ob_end_flush();
        }
    }

    /**
     * @param array<string, mixed> $data
     */
    private static function sendEvent(string $event, array $data): void
    {
        $json = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

        if (!is_string($json)) {
            $json = '{"error":"json_encode_failed"}';
        }

        echo 'event: ' . $event . "\n";
        echo 'data: ' . $json . "\n\n";

        @ob_flush();
        @flush();
    }
}