<?php

declare(strict_types=1);

namespace App\Framework\LiveComponents\Services;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Filesystem\FileStorage;
use App\Framework\LiveComponents\ValueObjects\QuarantineStatus;
use DateTimeImmutable;
use InvalidArgumentException;

/**
 * Quarantine Service
 *
 * Verwaltet Quarantine-Lifecycle für hochgeladene Dateien:
 * - Isolierung verdächtiger Dateien
 * - Integration mit externen Virus-Scannern
 * - Status-Tracking (pending → scanning → approved/rejected)
 * - Automatisches Cleanup nach Ablauf
 */
final readonly class QuarantineService
{
    public function __construct(
        private FileStorage $fileStorage,
        private string $quarantinePath = '/tmp/quarantine',
        private Duration $defaultRetentionPeriod = new Duration(86400) // 24 hours
    ) {
    }

    /**
     * Move file to quarantine area
     *
     * @param string $sourcePath Source file path
     * @param string $quarantineId Unique identifier for quarantined file
     * @return string Quarantine path
     * @throws InvalidArgumentException If source file doesn't exist
     */
    public function quarantine(string $sourcePath, string $quarantineId): string
    {
        if (!$this->fileStorage->exists($sourcePath)) {
            throw new InvalidArgumentException("Source file not found: {$sourcePath}");
        }

        // Create quarantine directory if needed
        if (!$this->fileStorage->exists($this->quarantinePath)) {
            $this->fileStorage->createDirectory($this->quarantinePath);
        }

        // Move file to quarantine
        $quarantinePath = $this->getQuarantinePath($quarantineId);
        $this->fileStorage->move($sourcePath, $quarantinePath);

        return $quarantinePath;
    }

    /**
     * Release file from quarantine to target location
     *
     * @param string $quarantineId Quarantine identifier
     * @param string $targetPath Target path for released file
     * @return void
     * @throws InvalidArgumentException If quarantined file doesn't exist
     */
    public function release(string $quarantineId, string $targetPath): void
    {
        $quarantinePath = $this->getQuarantinePath($quarantineId);

        if (!$this->fileStorage->exists($quarantinePath)) {
            throw new InvalidArgumentException("Quarantined file not found: {$quarantineId}");
        }

        // Ensure target directory exists
        $targetDir = dirname($targetPath);
        if (!$this->fileStorage->exists($targetDir)) {
            $this->fileStorage->createDirectory($targetDir);
        }

        // Move file from quarantine to target
        $this->fileStorage->move($quarantinePath, $targetPath);
    }

    /**
     * Delete quarantined file
     *
     * @param string $quarantineId Quarantine identifier
     * @return void
     */
    public function delete(string $quarantineId): void
    {
        $quarantinePath = $this->getQuarantinePath($quarantineId);

        if ($this->fileStorage->exists($quarantinePath)) {
            $this->fileStorage->delete($quarantinePath);
        }
    }

    /**
     * Check if file exists in quarantine
     *
     * @param string $quarantineId Quarantine identifier
     * @return bool True if quarantined file exists
     */
    public function exists(string $quarantineId): bool
    {
        return $this->fileStorage->exists($this->getQuarantinePath($quarantineId));
    }

    /**
     * Get quarantine file path
     *
     * @param string $quarantineId Quarantine identifier
     * @return string Quarantine file path
     */
    public function getQuarantinePath(string $quarantineId): string
    {
        return $this->quarantinePath . '/' . $quarantineId;
    }

    /**
     * Scan file with virus scanner hook
     *
     * Hook-based implementation - allows integration with external scanners:
     * - ClamAV
     * - VirusTotal API
     * - Custom scanning solutions
     *
     * @param string $quarantineId Quarantine identifier
     * @param callable $scannerHook Scanner callback: function(string $filePath): ScanResult
     * @return ScanResult Scan result with status and details
     * @throws InvalidArgumentException If quarantined file doesn't exist
     */
    public function scan(string $quarantineId, callable $scannerHook): ScanResult
    {
        $quarantinePath = $this->getQuarantinePath($quarantineId);

        if (!$this->fileStorage->exists($quarantinePath)) {
            throw new InvalidArgumentException("Quarantined file not found: {$quarantineId}");
        }

        // Execute scanner hook with file path
        $result = $scannerHook($quarantinePath);

        if (!$result instanceof ScanResult) {
            throw new InvalidArgumentException('Scanner hook must return ScanResult instance');
        }

        return $result;
    }

    /**
     * Validate status transition
     *
     * @param QuarantineStatus $currentStatus Current status
     * @param QuarantineStatus $newStatus Desired new status
     * @return bool True if transition is valid
     */
    public function canTransition(QuarantineStatus $currentStatus, QuarantineStatus $newStatus): bool
    {
        return $currentStatus->canTransitionTo($newStatus);
    }

    /**
     * Cleanup expired quarantined files
     *
     * Removes files that have been in quarantine longer than retention period.
     *
     * @param DateTimeImmutable|null $expiryTime Expiry threshold (default: now - retention period)
     * @return int Number of files deleted
     */
    public function cleanupExpired(?DateTimeImmutable $expiryTime = null): int
    {
        $expiryTime ??= (new DateTimeImmutable())->modify(
            sprintf('-%d seconds', $this->defaultRetentionPeriod->toSeconds())
        );

        $deleted = 0;

        if (!$this->fileStorage->exists($this->quarantinePath)) {
            return $deleted;
        }

        try {
            $files = $this->fileStorage->listDirectory($this->quarantinePath);

            foreach ($files as $file) {
                $modifiedTime = new DateTimeImmutable('@' . filemtime($file));

                if ($modifiedTime < $expiryTime) {
                    $this->fileStorage->delete($file);
                    $deleted++;
                }
            }
        } catch (\Exception $e) {
            // Directory might not exist or be inaccessible, ignore
        }

        return $deleted;
    }
}
