<?php

declare(strict_types=1);

namespace App\Framework\LiveComponents\Services;

use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheItem;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\LiveComponents\ValueObjects\ChunkHash;
use App\Framework\LiveComponents\ValueObjects\ChunkMetadata;
use App\Framework\LiveComponents\ValueObjects\QuarantineStatus;
use App\Framework\LiveComponents\ValueObjects\UploadSession;
use App\Framework\LiveComponents\ValueObjects\UploadSessionId;
use DateTimeImmutable;

/**
 * Cache-Based Upload Session Store
 *
 * Nutzt Framework's Cache für Session-Persistierung.
 * Optimal für Resume-Capability und Performance.
 */
final readonly class CacheUploadSessionStore implements UploadSessionStore
{
    private const KEY_PREFIX = 'upload_session:';
    private const DEFAULT_TTL_HOURS = 24;

    public function __construct(
        private Cache $cache
    ) {
    }

    public function save(UploadSession $session): void
    {
        $cacheKey = $this->getCacheKey($session->sessionId);
        $serialized = $this->serialize($session);

        // Calculate TTL from session expiry
        $now = new DateTimeImmutable();
        $ttlSeconds = $session->expiresAt->getTimestamp() - $now->getTimestamp();
        $ttl = Duration::fromSeconds(max(1, $ttlSeconds)); // At least 1 second

        $cacheItem = CacheItem::forSetting(
            key: $cacheKey,
            value: $serialized,
            ttl: $ttl
        );

        $this->cache->set($cacheItem);
    }

    public function get(UploadSessionId $sessionId): ?UploadSession
    {
        $cacheKey = $this->getCacheKey($sessionId);
        $cacheItem = $this->cache->get($cacheKey);

        if ($cacheItem === null || $cacheItem->value === null) {
            return null;
        }

        return $this->deserialize($cacheItem->value);
    }

    public function delete(UploadSessionId $sessionId): void
    {
        $cacheKey = $this->getCacheKey($sessionId);
        $this->cache->forget($cacheKey);
    }

    public function exists(UploadSessionId $sessionId): bool
    {
        return $this->get($sessionId) !== null;
    }

    public function cleanupExpired(): int
    {
        // Cache handles expiry automatically via TTL
        // This method is a no-op for cache-based implementation
        return 0;
    }

    private function getCacheKey(UploadSessionId $sessionId): CacheKey
    {
        return CacheKey::fromString(self::KEY_PREFIX . $sessionId->toString());
    }

    /**
     * Serialize session to array for cache storage
     */
    private function serialize(UploadSession $session): array
    {
        $chunks = [];
        foreach ($session->chunks as $chunk) {
            $chunks[] = [
                'index' => $chunk->index,
                'size' => $chunk->size->toBytes(),
                'hash' => $chunk->hash->toString(),
                'uploaded' => $chunk->uploaded,
            ];
        }

        return [
            'session_id' => $session->sessionId->toString(),
            'component_id' => $session->componentId,
            'file_name' => $session->fileName,
            'total_size' => $session->totalSize->toBytes(),
            'total_chunks' => $session->totalChunks,
            'chunks' => $chunks,
            'expected_file_hash' => $session->expectedFileHash?->toString(),
            'quarantine_status' => $session->quarantineStatus->value,
            'created_at' => $session->createdAt->format('Y-m-d H:i:s'),
            'completed_at' => $session->completedAt?->format('Y-m-d H:i:s'),
            'expires_at' => $session->expiresAt->format('Y-m-d H:i:s'),
        ];
    }

    /**
     * Deserialize array from cache to UploadSession
     */
    private function deserialize(array $data): UploadSession
    {
        $chunks = [];
        foreach ($data['chunks'] as $chunkData) {
            $chunks[] = new ChunkMetadata(
                index: $chunkData['index'],
                size: Byte::fromBytes($chunkData['size']),
                hash: ChunkHash::fromString($chunkData['hash']),
                uploaded: $chunkData['uploaded']
            );
        }

        return new UploadSession(
            sessionId: UploadSessionId::fromString($data['session_id']),
            componentId: $data['component_id'],
            fileName: $data['file_name'],
            totalSize: Byte::fromBytes($data['total_size']),
            totalChunks: $data['total_chunks'],
            chunks: $chunks,
            expectedFileHash: $data['expected_file_hash'] !== null
                ? ChunkHash::fromString($data['expected_file_hash'])
                : null,
            quarantineStatus: QuarantineStatus::from($data['quarantine_status']),
            createdAt: new DateTimeImmutable($data['created_at']),
            completedAt: $data['completed_at'] !== null
                ? new DateTimeImmutable($data['completed_at'])
                : null,
            expiresAt: new DateTimeImmutable($data['expires_at'])
        );
    }
}
