<?php

declare(strict_types=1);

namespace Tests\Framework\LiveComponents;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Filesystem\InMemoryStorage;
use App\Framework\LiveComponents\Services\QuarantineService;
use App\Framework\LiveComponents\ValueObjects\QuarantineStatus;
use App\Framework\LiveComponents\ValueObjects\ScanResult;
use App\Framework\LiveComponents\ValueObjects\ScanStatus;
use DateTimeImmutable;

beforeEach(function () {
    $this->fileStorage = new InMemoryStorage();
    $this->service = new QuarantineService(
        fileStorage: $this->fileStorage,
        quarantinePath: '/tmp/quarantine',
        defaultRetentionPeriod: Duration::fromHours(24)
    );
});

test('quarantines file successfully', function () {
    // Create source file
    $sourcePath = '/tmp/source/test-file.txt';
    $this->fileStorage->put($sourcePath, 'test content');

    // Quarantine file
    $quarantineId = 'test-quarantine-123';
    $quarantinePath = $this->service->quarantine($sourcePath, $quarantineId);

    // Verify file moved to quarantine
    expect($this->fileStorage->exists($quarantinePath))->toBeTrue();
    expect($this->fileStorage->exists($sourcePath))->toBeFalse();
    expect($quarantinePath)->toBe('/tmp/quarantine/' . $quarantineId);
});

test('throws exception when quarantining non-existent file', function () {
    $nonExistentPath = '/tmp/does-not-exist.txt';
    $quarantineId = 'test-quarantine-123';

    expect(fn() => $this->service->quarantine($nonExistentPath, $quarantineId))
        ->toThrow(\InvalidArgumentException::class, 'Source file not found');
});

test('releases quarantined file to target location', function () {
    // Setup: quarantine a file
    $sourcePath = '/tmp/source/file.txt';
    $this->fileStorage->put($sourcePath, 'quarantined content');
    $quarantineId = 'release-test-456';
    $this->service->quarantine($sourcePath, $quarantineId);

    // Release file
    $targetPath = '/tmp/target/released-file.txt';
    $this->service->release($quarantineId, $targetPath);

    // Verify file moved to target
    expect($this->fileStorage->exists($targetPath))->toBeTrue();
    expect($this->service->exists($quarantineId))->toBeFalse();
    expect($this->fileStorage->get($targetPath))->toBe('quarantined content');
});

test('throws exception when releasing non-existent quarantined file', function () {
    $nonExistentQuarantineId = 'does-not-exist';
    $targetPath = '/tmp/target/file.txt';

    expect(fn() => $this->service->release($nonExistentQuarantineId, $targetPath))
        ->toThrow(\InvalidArgumentException::class, 'Quarantined file not found');
});

test('deletes quarantined file', function () {
    // Setup: quarantine a file
    $sourcePath = '/tmp/source/to-delete.txt';
    $this->fileStorage->put($sourcePath, 'delete me');
    $quarantineId = 'delete-test-789';
    $this->service->quarantine($sourcePath, $quarantineId);

    // Delete quarantined file
    $this->service->delete($quarantineId);

    // Verify file deleted
    expect($this->service->exists($quarantineId))->toBeFalse();
});

test('delete handles non-existent quarantine gracefully', function () {
    $nonExistentId = 'never-existed';

    // Should not throw exception
    $this->service->delete($nonExistentId);

    expect($this->service->exists($nonExistentId))->toBeFalse();
});

test('checks quarantine existence correctly', function () {
    $sourcePath = '/tmp/source/exist-check.txt';
    $this->fileStorage->put($sourcePath, 'existence test');
    $quarantineId = 'exist-test-101';

    // Before quarantine
    expect($this->service->exists($quarantineId))->toBeFalse();

    // After quarantine
    $this->service->quarantine($sourcePath, $quarantineId);
    expect($this->service->exists($quarantineId))->toBeTrue();

    // After deletion
    $this->service->delete($quarantineId);
    expect($this->service->exists($quarantineId))->toBeFalse();
});

test('gets correct quarantine path', function () {
    $quarantineId = 'path-test-202';
    $expectedPath = '/tmp/quarantine/' . $quarantineId;

    $actualPath = $this->service->getQuarantinePath($quarantineId);

    expect($actualPath)->toBe($expectedPath);
});

test('scans file with clean result', function () {
    // Setup: quarantine a file
    $sourcePath = '/tmp/source/clean-file.txt';
    $this->fileStorage->put($sourcePath, 'clean content');
    $quarantineId = 'scan-clean-303';
    $this->service->quarantine($sourcePath, $quarantineId);

    // Mock scanner that returns clean
    $scannerHook = function (string $filePath): ScanResult {
        return ScanResult::clean('No threats detected');
    };

    // Scan file
    $result = $this->service->scan($quarantineId, $scannerHook);

    expect($result->isClean())->toBeTrue();
    expect($result->status)->toBe(ScanStatus::CLEAN);
});

test('scans file with infected result', function () {
    // Setup: quarantine a file
    $sourcePath = '/tmp/source/infected-file.txt';
    $this->fileStorage->put($sourcePath, 'infected content');
    $quarantineId = 'scan-infected-404';
    $this->service->quarantine($sourcePath, $quarantineId);

    // Mock scanner that detects threat
    $scannerHook = function (string $filePath): ScanResult {
        return ScanResult::infected(
            threatName: 'Trojan.Generic',
            confidenceScore: 0.95,
            details: 'Malicious payload detected'
        );
    };

    // Scan file
    $result = $this->service->scan($quarantineId, $scannerHook);

    expect($result->isInfected())->toBeTrue();
    expect($result->threatName)->toBe('Trojan.Generic');
    expect($result->confidenceScore)->toBe(0.95);
});

test('scans file with suspicious result', function () {
    // Setup: quarantine a file
    $sourcePath = '/tmp/source/suspicious-file.txt';
    $this->fileStorage->put($sourcePath, 'suspicious content');
    $quarantineId = 'scan-suspicious-505';
    $this->service->quarantine($sourcePath, $quarantineId);

    // Mock scanner that finds suspicious patterns
    $scannerHook = function (string $filePath): ScanResult {
        return ScanResult::suspicious(
            details: 'Potentially unwanted program',
            confidenceScore: 0.65
        );
    };

    // Scan file
    $result = $this->service->scan($quarantineId, $scannerHook);

    expect($result->isSuspicious())->toBeTrue();
    expect($result->shouldQuarantine())->toBeTrue();
});

test('throws exception when scanning non-existent quarantined file', function () {
    $nonExistentId = 'never-scanned';
    $scannerHook = fn($path) => ScanResult::clean();

    expect(fn() => $this->service->scan($nonExistentId, $scannerHook))
        ->toThrow(\InvalidArgumentException::class, 'Quarantined file not found');
});

test('throws exception when scanner hook returns invalid type', function () {
    // Setup: quarantine a file
    $sourcePath = '/tmp/source/invalid-scanner.txt';
    $this->fileStorage->put($sourcePath, 'content');
    $quarantineId = 'invalid-scanner-606';
    $this->service->quarantine($sourcePath, $quarantineId);

    // Invalid scanner hook that doesn't return ScanResult
    $invalidHook = function (string $filePath): array {
        return ['status' => 'clean'];
    };

    expect(fn() => $this->service->scan($quarantineId, $invalidHook))
        ->toThrow(\InvalidArgumentException::class, 'Scanner hook must return ScanResult instance');
});

test('validates status transitions correctly', function () {
    // Valid transitions
    expect($this->service->canTransition(
        QuarantineStatus::PENDING,
        QuarantineStatus::SCANNING
    ))->toBeTrue();

    expect($this->service->canTransition(
        QuarantineStatus::SCANNING,
        QuarantineStatus::APPROVED
    ))->toBeTrue();

    expect($this->service->canTransition(
        QuarantineStatus::SCANNING,
        QuarantineStatus::REJECTED
    ))->toBeTrue();

    // Invalid transitions
    expect($this->service->canTransition(
        QuarantineStatus::APPROVED,
        QuarantineStatus::PENDING
    ))->toBeFalse();

    expect($this->service->canTransition(
        QuarantineStatus::REJECTED,
        QuarantineStatus::APPROVED
    ))->toBeFalse();
});

test('cleans up expired quarantined files', function () {
    // Create multiple files with different ages
    $oldFile1 = '/tmp/source/old-file-1.txt';
    $oldFile2 = '/tmp/source/old-file-2.txt';
    $recentFile = '/tmp/source/recent-file.txt';

    $this->fileStorage->put($oldFile1, 'old content 1');
    $this->fileStorage->put($oldFile2, 'old content 2');
    $this->fileStorage->put($recentFile, 'recent content');

    $this->service->quarantine($oldFile1, 'old-1');
    $this->service->quarantine($oldFile2, 'old-2');
    $this->service->quarantine($recentFile, 'recent-1');

    // Simulate old files by using expiry time in future
    // (files older than 48 hours should be deleted)
    $expiryTime = (new DateTimeImmutable())->modify('+48 hours');

    $deletedCount = $this->service->cleanupExpired($expiryTime);

    // All files should be deleted as they're "older" than expiry time
    expect($deletedCount)->toBe(3);
    expect($this->service->exists('old-1'))->toBeFalse();
    expect($this->service->exists('old-2'))->toBeFalse();
    expect($this->service->exists('recent-1'))->toBeFalse();
});

test('cleanup handles empty quarantine directory gracefully', function () {
    // No quarantined files
    $deletedCount = $this->service->cleanupExpired();

    expect($deletedCount)->toBe(0);
});

test('creates quarantine directory if not exists', function () {
    $sourcePath = '/tmp/source/first-file.txt';
    $this->fileStorage->put($sourcePath, 'first quarantine');

    // Quarantine directory doesn't exist yet
    expect($this->fileStorage->exists('/tmp/quarantine'))->toBeFalse();

    // Quarantine file - should create directory
    $this->service->quarantine($sourcePath, 'first-quarantine');

    // Directory should now exist
    expect($this->fileStorage->exists('/tmp/quarantine'))->toBeTrue();
});

test('creates target directory if not exists during release', function () {
    // Setup: quarantine a file
    $sourcePath = '/tmp/source/release-target-test.txt';
    $this->fileStorage->put($sourcePath, 'release content');
    $quarantineId = 'release-dir-test';
    $this->service->quarantine($sourcePath, $quarantineId);

    // Target directory doesn't exist
    $targetPath = '/tmp/new-target-dir/released-file.txt';
    expect($this->fileStorage->exists('/tmp/new-target-dir'))->toBeFalse();

    // Release - should create target directory
    $this->service->release($quarantineId, $targetPath);

    // Directory and file should exist
    expect($this->fileStorage->exists('/tmp/new-target-dir'))->toBeTrue();
    expect($this->fileStorage->exists($targetPath))->toBeTrue();
});
