<?php

declare(strict_types=1);

namespace App\Framework\Sse\Controllers;

use App\Framework\Attributes\Route;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\Method;
use App\Framework\Http\SseStream;
use App\Framework\Router\Result\SseResult;
use App\Framework\Sse\SseChannelAuthorizer;
use App\Framework\Sse\SseConnectionPool;
use App\Framework\Sse\ValueObjects\ConnectionId;
use App\Framework\Sse\ValueObjects\SseChannel;
use App\Framework\Sse\ValueObjects\SseConnection;

/**
 * SSE Stream Controller
 *
 * Provides Server-Sent Events streaming endpoint for LiveComponents.
 *
 * Route: GET /sse/stream?channels=presence,component:counter:demo
 *
 * Features:
 * - Multi-channel subscription
 * - Authentication support
 * - Auto-heartbeat (handled by SseEmitter)
 * - Connection tracking
 */
final readonly class SseStreamController
{
    public function __construct(
        private SseConnectionPool $connectionPool,
        private SseChannelAuthorizer $authorizer
    ) {
    }

    #[Route('/sse/stream', method: Method::GET)]
    public function stream(HttpRequest $request): SseResult
    {
        // Parse channel subscriptions from query params
        $channelNames = $this->parseChannelNames($request);

        if (empty($channelNames)) {
            throw new \InvalidArgumentException('At least one channel must be specified');
        }

        // Convert to SseChannel objects
        $channels = $this->parseChannels($channelNames);

        // Get user ID from session/auth (if available)
        $userId = $this->getUserId($request);

        // Authorize channel access
        $this->authorizeChannels($channels, $userId);

        // Return SseResult with callback
        // Heartbeats are automatically handled by SseEmitter every 30s
        return new SseResult(
            callback         : function (SseStream $stream) use ($channels, $userId) {
                $this->handleConnection($stream, $channels, $userId);
            },
            maxDuration      : 0,
            heartbeatInterval: 30  // No max duration - keep connection open
        );
    }

    /**
     * Handle the SSE connection lifecycle
     */
    private function handleConnection(SseStream $stream, array $channels, ?string $userId): void
    {
        // Generate unique connection ID
        $connectionId = ConnectionId::generate();

        // Create connection object
        $connection = new SseConnection(
            connectionId: $connectionId,
            stream: $stream,
            channels: $channels,
            userId: $userId
        );

        // Add to connection pool
        try {
            $this->connectionPool->add($connection);
        } catch (\RuntimeException $e) {
            // Connection limit reached
            $stream->sendJson([
                'error' => $e->getMessage(),
                'type' => 'connection_limit_exceeded',
            ], 'error');

            return;
        }

        // Send initial connection confirmation
        $stream->sendJson([
            'connection_id' => $connectionId->toString(),
            'channels' => array_map(fn ($c) => $c->toString(), $channels),
            'timestamp' => time(),
        ], 'connected');

        // Keep connection alive until client disconnects
        // SseEmitter handles heartbeats automatically
        try {
            while ($stream->isConnectionActive() && $stream->isActive()) {
                // Just sleep - broadcasts happen via SseBroadcaster
                // Heartbeats are sent by SseEmitter
                sleep(1);
            }
        } finally {
            // Cleanup connection when disconnected
            $this->connectionPool->remove($connectionId);

            // Send disconnect event if stream is still active
            if ($stream->isActive()) {
                $stream->sendJson([
                    'connection_id' => $connectionId->toString(),
                    'timestamp' => time(),
                ], 'disconnected');
            }
        }
    }

    /**
     * Parse channel names from query params
     *
     * @return array<string>
     */
    private function parseChannelNames(HttpRequest $request): array
    {
        $channelsParam = $request->queryParameters->get('channels', '');

        if (empty($channelsParam)) {
            return [];
        }

        return array_filter(
            array_map('trim', explode(',', $channelsParam)),
            fn ($name) => ! empty($name)
        );
    }

    /**
     * Parse channel names to SseChannel objects
     *
     * @param array<string> $channelNames
     * @return array<SseChannel>
     */
    private function parseChannels(array $channelNames): array
    {
        return array_map(
            fn (string $name) => SseChannel::fromString($name),
            $channelNames
        );
    }

    /**
     * Get user ID from request (session/auth)
     */
    private function getUserId(HttpRequest $request): ?string
    {
        // TODO: Integrate with Auth system
        // For now, check session
        $session = $request->session;

        if ($session !== null && $session->has('user_id')) {
            return (string) $session->get('user_id');
        }

        return null;
    }

    /**
     * Authorize channel access
     *
     * @param array<SseChannel> $channels
     */
    private function authorizeChannels(array $channels, ?string $userId): void
    {
        foreach ($channels as $channel) {
            if (! $this->authorizer->canSubscribe($channel, $userId)) {
                throw new \RuntimeException("Unauthorized access to channel: {$channel->toString()}");
            }
        }
    }
}
