<?php

declare(strict_types=1);

use App\Framework\Cache\Compression\GzipCompression;
use App\Framework\Cache\Compression\NoCompression;

describe('NoCompression', function () {
    beforeEach(function () {
        $this->compression = new NoCompression();
    });

    describe('Basic Operations', function () {
        it('returns value unchanged on compress', function () {
            $original = 'Hello, World!';
            $compressed = $this->compression->compress($original);

            expect($compressed)->toBe($original);
        });

        it('returns value unchanged on decompress', function () {
            $original = 'Hello, World!';
            $decompressed = $this->compression->decompress($original);

            expect($decompressed)->toBe($original);
        });

        it('reports values as never compressed', function () {
            expect($this->compression->isCompressed('any value'))->toBeFalse();
            expect($this->compression->isCompressed(''))->toBeFalse();
            expect($this->compression->isCompressed('gz:prefixed'))->toBeFalse();
        });
    });

    describe('Force Compression Flag', function () {
        it('ignores force compression flag', function () {
            $original = 'test';
            $compressed = $this->compression->compress($original, forceCompression: true);

            expect($compressed)->toBe($original);
        });
    });

    describe('Edge Cases', function () {
        it('handles empty strings', function () {
            $result = $this->compression->compress('');
            expect($result)->toBe('');

            $result = $this->compression->decompress('');
            expect($result)->toBe('');
        });

        it('handles large strings', function () {
            $large = str_repeat('abcdefghij', 10000); // 100KB
            $compressed = $this->compression->compress($large);

            expect($compressed)->toBe($large);
        });

        it('handles binary data', function () {
            $binary = random_bytes(1000);
            $compressed = $this->compression->compress($binary);

            expect($compressed)->toBe($binary);
        });
    });
});

describe('GzipCompression', function () {
    describe('Basic Operations', function () {
        it('compresses strings above threshold', function () {
            $compression = new GzipCompression(compressionLevel: 6, minLengthToCompress: 100);
            $original = str_repeat('a', 200); // Above threshold

            $compressed = $compression->compress($original);

            // Verify compression occurred
            $isCompressed = $compression->isCompressed($compressed);
            $hasPrefix = str_starts_with($compressed, 'gz:');
            $isDifferent = $compressed !== $original;

            expect($isCompressed)->toBeTrue();
            expect($hasPrefix)->toBeTrue();
            expect($isDifferent)->toBeTrue();
        });

        it('does not compress strings below threshold', function () {
            $compression = new GzipCompression(compressionLevel: 6, minLengthToCompress: 1000);
            $original = 'Short string';

            $compressed = $compression->compress($original);

            expect($compressed)->toBe($original);
            expect($compression->isCompressed($compressed))->toBeFalse();
        });

        it('decompresses compressed strings', function () {
            $compression = new GzipCompression(compressionLevel: 6, minLengthToCompress: 100);
            $original = str_repeat('test data ', 50); // 500 bytes

            $compressed = $compression->compress($original);
            $decompressed = $compression->decompress($compressed);

            expect($decompressed)->toBe($original);
        });

        it('returns uncompressed strings unchanged on decompress', function () {
            $compression = new GzipCompression();
            $original = 'Not compressed';

            $decompressed = $compression->decompress($original);

            expect($decompressed)->toBe($original);
        });
    });

    describe('Force Compression', function () {
        it('forces compression even below threshold', function () {
            $compression = new GzipCompression(compressionLevel: 6, minLengthToCompress: 1000);
            $original = 'Short';

            $compressed = $compression->compress($original, forceCompression: true);
            $hasPrefix = str_starts_with($compressed, 'gz:');

            expect($compression->isCompressed($compressed))->toBeTrue();
            expect($hasPrefix)->toBeTrue();
        });
    });

    describe('Compression Levels', function () {
        it('uses default compression level -1', function () {
            $compression = new GzipCompression();
            $original = str_repeat('data ', 500);

            $compressed = $compression->compress($original);

            expect($compression->isCompressed($compressed))->toBeTrue();
        });

        it('uses maximum compression level 9', function () {
            $compression = new GzipCompression(compressionLevel: 9, minLengthToCompress: 100);
            $original = str_repeat('test ', 500);

            $compressed = $compression->compress($original);
            $isSmallerThanOriginal = strlen($compressed) < strlen($original);

            expect($compression->isCompressed($compressed))->toBeTrue();
            expect($isSmallerThanOriginal)->toBeTrue();
        });

        it('uses minimum compression level 1', function () {
            $compression = new GzipCompression(compressionLevel: 1, minLengthToCompress: 100);
            $original = str_repeat('test ', 500);

            $compressed = $compression->compress($original);

            expect($compression->isCompressed($compressed))->toBeTrue();
        });
    });

    describe('Compression Efficiency', function () {
        it('achieves significant compression on repetitive data', function () {
            $compression = new GzipCompression(compressionLevel: 6, minLengthToCompress: 100);
            $original = str_repeat('abcdefghij', 1000); // 10KB repetitive

            $compressed = $compression->compress($original);
            $compressionRatio = strlen($compressed) / strlen($original);

            $isEfficient = $compressionRatio < 0.1; // Should compress to < 10%
            expect($isEfficient)->toBeTrue();
        });

        it('has minimal compression on random data', function () {
            $compression = new GzipCompression(compressionLevel: 6, minLengthToCompress: 100);
            $original = random_bytes(5000);

            $compressed = $compression->compress($original);
            $compressionRatio = strlen($compressed) / strlen($original);

            // Random data doesn't compress well, ratio likely > 0.95
            $hasMinimalCompression = $compressionRatio > 0.8;
            expect($hasMinimalCompression)->toBeTrue();
        });
    });

    describe('Error Handling', function () {
        it('returns result on compression attempt', function () {
            $compression = new GzipCompression();
            $original = 'test';

            $result = $compression->compress($original, forceCompression: true);

            $isNotNull = $result !== null;
            expect($isNotNull)->toBeTrue();
        });

        it('handles decompression of corrupted data gracefully', function () {
            $compression = new GzipCompression();
            $corrupted = 'gz:corrupted_data_not_valid_gzip';

            $decompressed = $compression->decompress($corrupted);

            // Should return the original value as fallback
            expect($decompressed)->toBe($corrupted);
        });
    });

    describe('Edge Cases', function () {
        it('handles empty strings', function () {
            $compression = new GzipCompression(minLengthToCompress: 0);

            $compressed = $compression->compress('', forceCompression: true);
            $decompressed = $compression->decompress($compressed);

            expect($decompressed)->toBe('');
        });

        it('handles very large strings', function () {
            $compression = new GzipCompression(compressionLevel: 6, minLengthToCompress: 1000);
            $large = str_repeat('Large data block ', 10000); // ~170KB

            $compressed = $compression->compress($large);
            $decompressed = $compression->decompress($compressed);
            $isSmallerThanOriginal = strlen($compressed) < strlen($large);

            expect($decompressed)->toBe($large);
            expect($isSmallerThanOriginal)->toBeTrue();
        });

        it('handles strings with special characters', function () {
            $compression = new GzipCompression(compressionLevel: 6, minLengthToCompress: 100);
            $special = str_repeat('äöü€₹🚀', 100);

            $compressed = $compression->compress($special);
            $decompressed = $compression->decompress($compressed);

            expect($decompressed)->toBe($special);
        });
    });

    describe('Prefix Detection', function () {
        it('correctly identifies compressed strings by prefix', function () {
            $compression = new GzipCompression(minLengthToCompress: 100);

            expect($compression->isCompressed('gz:anything'))->toBeTrue();
            expect($compression->isCompressed('not compressed'))->toBeFalse();
            expect($compression->isCompressed('gzcompressed'))->toBeFalse(); // No colon
            expect($compression->isCompressed(''))->toBeFalse();
        });

        it('strips prefix on decompression', function () {
            $compression = new GzipCompression(compressionLevel: 6, minLengthToCompress: 100);
            $original = str_repeat('test ', 200);

            $compressed = $compression->compress($original);
            $hasPrefix = str_starts_with($compressed, 'gz:');

            // The compressed value should have the prefix
            expect($hasPrefix)->toBeTrue();

            // Decompression should remove it
            $decompressed = $compression->decompress($compressed);
            $doesNotHavePrefix = !str_starts_with($decompressed, 'gz:');

            expect($doesNotHavePrefix)->toBeTrue();
            expect($decompressed)->toBe($original);
        });
    });
});
