<?php

declare(strict_types=1);

namespace App\Framework\LiveComponents\Services;

use App\Framework\Sse\SseBroadcaster;
use App\Framework\LiveComponents\ValueObjects\UploadSession;
use App\Framework\LiveComponents\ValueObjects\UploadSessionId;

/**
 * Upload Progress Tracker
 *
 * Tracks chunked upload progress and broadcasts updates via SSE.
 * Integrates with ChunkedUploadManager für Real-time Progress-Updates.
 */
final readonly class UploadProgressTracker implements UploadProgressTrackerInterface
{
    public function __construct(
        private SseBroadcaster $sseBroadcaster,
        private UploadSessionStore $sessionStore
    ) {
    }

    /**
     * Broadcast upload initialized event
     *
     * @param UploadSession $session Upload session
     * @param string $userId User identifier for SSE channel
     * @return int Number of clients notified
     */
    public function broadcastInitialized(UploadSession $session, string $userId): int
    {
        return $this->sseBroadcaster->broadcastProgress(
            userId: $userId,
            taskId: $session->sessionId->toString(),
            percent: 0,
            message: sprintf('Upload initialized: %s', $session->fileName),
            data: [
                'session_id' => $session->sessionId->toString(),
                'file_name' => $session->fileName,
                'total_size' => $session->totalSize->toBytes(),
                'total_chunks' => $session->totalChunks,
                'phase' => 'initialized',
            ]
        );
    }

    /**
     * Broadcast chunk uploaded event
     *
     * @param UploadSession $session Upload session
     * @param string $userId User identifier for SSE channel
     * @return int Number of clients notified
     */
    public function broadcastChunkUploaded(UploadSession $session, string $userId): int
    {
        $progress = $session->getProgress();

        return $this->sseBroadcaster->broadcastProgress(
            userId: $userId,
            taskId: $session->sessionId->toString(),
            percent: (int) round($progress),
            message: sprintf(
                'Uploading %s: %d/%d chunks',
                $session->fileName,
                count($session->getUploadedChunks()),
                $session->totalChunks
            ),
            data: [
                'session_id' => $session->sessionId->toString(),
                'uploaded_chunks' => count($session->getUploadedChunks()),
                'total_chunks' => $session->totalChunks,
                'uploaded_bytes' => $session->getUploadedBytes()->toBytes(),
                'total_bytes' => $session->totalSize->toBytes(),
                'phase' => 'uploading',
            ]
        );
    }

    /**
     * Broadcast upload complete event
     *
     * @param UploadSession $session Upload session
     * @param string $userId User identifier for SSE channel
     * @return int Number of clients notified
     */
    public function broadcastCompleted(UploadSession $session, string $userId): int
    {
        return $this->sseBroadcaster->broadcastProgress(
            userId: $userId,
            taskId: $session->sessionId->toString(),
            percent: 100,
            message: sprintf('Upload complete: %s', $session->fileName),
            data: [
                'session_id' => $session->sessionId->toString(),
                'file_name' => $session->fileName,
                'total_size' => $session->totalSize->toBytes(),
                'phase' => 'completed',
                'completed_at' => $session->completedAt?->format('Y-m-d H:i:s'),
            ]
        );
    }

    /**
     * Broadcast upload aborted event
     *
     * @param UploadSessionId $sessionId Session identifier
     * @param string $userId User identifier for SSE channel
     * @param string $reason Abort reason
     * @return int Number of clients notified
     */
    public function broadcastAborted(UploadSessionId $sessionId, string $userId, string $reason = 'User cancelled'): int
    {
        return $this->sseBroadcaster->broadcastProgress(
            userId: $userId,
            taskId: $sessionId->toString(),
            percent: 0,
            message: sprintf('Upload aborted: %s', $reason),
            data: [
                'session_id' => $sessionId->toString(),
                'phase' => 'aborted',
                'reason' => $reason,
            ]
        );
    }

    /**
     * Broadcast upload error event
     *
     * @param UploadSessionId $sessionId Session identifier
     * @param string $userId User identifier for SSE channel
     * @param string $error Error message
     * @return int Number of clients notified
     */
    public function broadcastError(UploadSessionId $sessionId, string $userId, string $error): int
    {
        // Get current session for progress info
        $session = $this->sessionStore->get($sessionId);
        $progress = $session ? $session->getProgress() : 0;

        return $this->sseBroadcaster->broadcastProgress(
            userId: $userId,
            taskId: $sessionId->toString(),
            percent: (int) round($progress),
            message: sprintf('Upload error: %s', $error),
            data: [
                'session_id' => $sessionId->toString(),
                'phase' => 'error',
                'error' => $error,
            ]
        );
    }

    /**
     * Broadcast quarantine status change
     *
     * @param UploadSession $session Upload session
     * @param string $userId User identifier for SSE channel
     * @return int Number of clients notified
     */
    public function broadcastQuarantineStatus(UploadSession $session, string $userId): int
    {
        return $this->sseBroadcaster->broadcastProgress(
            userId: $userId,
            taskId: $session->sessionId->toString(),
            percent: 100,
            message: sprintf(
                'File %s: %s',
                $session->fileName,
                $this->getQuarantineMessage($session)
            ),
            data: [
                'session_id' => $session->sessionId->toString(),
                'file_name' => $session->fileName,
                'phase' => 'quarantine',
                'quarantine_status' => $session->quarantineStatus->value,
            ]
        );
    }

    /**
     * Get human-readable quarantine message
     */
    private function getQuarantineMessage(UploadSession $session): string
    {
        return match ($session->quarantineStatus) {
            \App\Framework\LiveComponents\ValueObjects\QuarantineStatus::PENDING => 'Awaiting security scan',
            \App\Framework\LiveComponents\ValueObjects\QuarantineStatus::SCANNING => 'Security scan in progress',
            \App\Framework\LiveComponents\ValueObjects\QuarantineStatus::APPROVED => 'Security scan passed',
            \App\Framework\LiveComponents\ValueObjects\QuarantineStatus::REJECTED => 'Security scan failed',
            \App\Framework\LiveComponents\ValueObjects\QuarantineStatus::EXPIRED => 'Security scan expired',
        };
    }

    /**
     * Get current session progress
     *
     * @param UploadSessionId $sessionId Session identifier
     * @return array{progress: float, uploaded_chunks: int, total_chunks: int, phase: string}|null
     */
    public function getProgress(UploadSessionId $sessionId): ?array
    {
        $session = $this->sessionStore->get($sessionId);

        if ($session === null) {
            return null;
        }

        return [
            'progress' => $session->getProgress(),
            'uploaded_chunks' => count($session->getUploadedChunks()),
            'total_chunks' => $session->totalChunks,
            'uploaded_bytes' => $session->getUploadedBytes()->toBytes(),
            'total_bytes' => $session->totalSize->toBytes(),
            'phase' => $this->determinePhase($session),
            'quarantine_status' => $session->quarantineStatus->value,
        ];
    }

    /**
     * Determine current upload phase
     */
    private function determinePhase(UploadSession $session): string
    {
        if ($session->completedAt !== null) {
            return 'completed';
        }

        if ($session->isExpired()) {
            return 'expired';
        }

        if (empty($session->chunks)) {
            return 'initialized';
        }

        return 'uploading';
    }
}
