<?php

declare(strict_types=1);

use App\Framework\Filesystem\CachedFileStorage;
use App\Framework\Filesystem\FileStorage;
use App\Framework\Filesystem\Exceptions\FileNotFoundException;
use App\Framework\Filesystem\Exceptions\DirectoryCreateException;

// Helper function for recursive directory deletion
function deleteDirectoryRecursive(string $dir): void
{
    if (!is_dir($dir)) {
        return;
    }

    $files = array_diff(scandir($dir), ['.', '..']);

    foreach ($files as $file) {
        $path = $dir . '/' . $file;
        if (is_dir($path)) {
            deleteDirectoryRecursive($path);
        } else {
            unlink($path);
        }
    }

    rmdir($dir);
}

describe('CachedFileStorage', function () {
    beforeEach(function () {
        // Create temporary test directory
        $this->testDir = sys_get_temp_dir() . '/cached_storage_test_' . uniqid();
        mkdir($this->testDir, 0777, true);

        // Create wrapped storage
        $this->storage = new FileStorage(basePath: $this->testDir);
        $this->cachedStorage = new CachedFileStorage($this->storage, basePath: $this->testDir);
    });

    afterEach(function () {
        // Cleanup test directory
        if (is_dir($this->testDir)) {
            deleteDirectoryRecursive($this->testDir);
        }
    });


    describe('Directory Caching', function () {
        it('caches directory on first put', function () {
            $path = 'subdir/file.txt';
            $content = 'test content';

            // First put - directory will be created and cached
            $this->cachedStorage->put($path, $content);

            $stats = $this->cachedStorage->getCacheStats();
            expect($stats['cached_directories'])->toBeGreaterThan(0);

            // Verify directory exists
            $dir = $this->testDir . '/subdir';
            expect(is_dir($dir))->toBeTrue();
        });

        it('reuses cached directory on subsequent puts', function () {
            $dir = 'nested/deep/directory';

            // First put - creates and caches directory
            $this->cachedStorage->put($dir . '/file1.txt', 'content1');

            $statsBefore = $this->cachedStorage->getCacheStats();
            $cacheSizeBefore = $statsBefore['cached_directories'];

            // Second put - reuses cached directory (no new is_dir call)
            $this->cachedStorage->put($dir . '/file2.txt', 'content2');

            $statsAfter = $this->cachedStorage->getCacheStats();
            $cacheSizeAfter = $statsAfter['cached_directories'];

            // Cache size should be same (directory already cached)
            expect($cacheSizeAfter)->toBe($cacheSizeBefore);

            // Both files should exist
            expect(file_exists($this->testDir . '/' . $dir . '/file1.txt'))->toBeTrue();
            expect(file_exists($this->testDir . '/' . $dir . '/file2.txt'))->toBeTrue();
        });

        it('caches parent directories recursively', function () {
            $path = 'level1/level2/level3/file.txt';

            $this->cachedStorage->put($path, 'test');

            $stats = $this->cachedStorage->getCacheStats();

            // Should cache level1, level1/level2, and level1/level2/level3
            expect($stats['cached_directories'])->toBeGreaterThanOrEqual(3);
        });

        it('handles absolute paths correctly', function () {
            $absolutePath = $this->testDir . '/absolute/file.txt';

            $this->cachedStorage->put($absolutePath, 'test');

            $stats = $this->cachedStorage->getCacheStats();
            expect($stats['cached_directories'])->toBeGreaterThan(0);

            expect(file_exists($absolutePath))->toBeTrue();
        });
    });

    describe('Read Operations', function () {
        it('delegates get to wrapped storage', function () {
            $path = 'test.txt';
            $content = 'test content';

            // Write file directly via wrapped storage
            $this->storage->put($path, $content);

            // Read via cached storage
            $result = $this->cachedStorage->get($path);

            expect($result)->toBe($content);
        });

        it('throws FileNotFoundException for non-existent files', function () {
            expect(fn() => $this->cachedStorage->get('nonexistent.txt'))
                ->toThrow(FileNotFoundException::class);
        });

        it('checks file existence correctly', function () {
            $path = 'exists.txt';

            expect($this->cachedStorage->exists($path))->toBeFalse();

            $this->cachedStorage->put($path, 'content');

            expect($this->cachedStorage->exists($path))->toBeTrue();
        });
    });

    describe('Write Operations', function () {
        it('creates nested directories automatically', function () {
            $path = 'deep/nested/structure/file.txt';
            $content = 'nested content';

            $this->cachedStorage->put($path, $content);

            expect(file_exists($this->testDir . '/' . $path))->toBeTrue();
            expect(file_get_contents($this->testDir . '/' . $path))->toBe($content);
        });

        it('overwrites existing files', function () {
            $path = 'overwrite.txt';

            $this->cachedStorage->put($path, 'original');
            expect($this->cachedStorage->get($path))->toBe('original');

            $this->cachedStorage->put($path, 'updated');
            expect($this->cachedStorage->get($path))->toBe('updated');
        });

        it('handles empty content', function () {
            $path = 'empty.txt';

            $this->cachedStorage->put($path, '');

            expect($this->cachedStorage->exists($path))->toBeTrue();
            expect($this->cachedStorage->get($path))->toBe('');
        });
    });

    describe('Copy Operations', function () {
        it('copies files with directory cache optimization', function () {
            $source = 'source.txt';
            $destination = 'subdir/destination.txt';

            // Create source file
            $this->cachedStorage->put($source, 'copy this');

            // Copy to nested directory (should cache destination directory)
            $this->cachedStorage->copy($source, $destination);

            expect($this->cachedStorage->exists($destination))->toBeTrue();
            expect($this->cachedStorage->get($destination))->toBe('copy this');

            $stats = $this->cachedStorage->getCacheStats();
            expect($stats['cached_directories'])->toBeGreaterThan(0);
        });

        it('throws exception when copying non-existent file', function () {
            expect(fn() => $this->cachedStorage->copy('nonexistent.txt', 'destination.txt'))
                ->toThrow(FileNotFoundException::class);
        });
    });


    describe('Delete Operations', function () {
        it('deletes files', function () {
            $path = 'delete-me.txt';

            $this->cachedStorage->put($path, 'content');
            expect($this->cachedStorage->exists($path))->toBeTrue();

            $this->cachedStorage->delete($path);
            expect($this->cachedStorage->exists($path))->toBeFalse();
        });

        it('throws exception when deleting non-existent file', function () {
            expect(fn() => $this->cachedStorage->delete('nonexistent.txt'))
                ->toThrow(FileNotFoundException::class);
        });

        it('keeps directory in cache after file deletion', function () {
            $path = 'subdir/file.txt';

            $this->cachedStorage->put($path, 'content');

            $statsBefore = $this->cachedStorage->getCacheStats();
            $cacheSizeBefore = $statsBefore['cached_directories'];

            $this->cachedStorage->delete($path);

            $statsAfter = $this->cachedStorage->getCacheStats();
            $cacheSizeAfter = $statsAfter['cached_directories'];

            // Directory should still be cached (still exists, just empty)
            expect($cacheSizeAfter)->toBe($cacheSizeBefore);
        });
    });

    describe('Metadata Operations', function () {
        it('gets file size', function () {
            $path = 'sized.txt';
            $content = 'test content with size';

            $this->cachedStorage->put($path, $content);

            $size = $this->cachedStorage->size($path);

            expect($size)->toBe(strlen($content));
        });

        it('gets last modified timestamp', function () {
            $path = 'timestamped.txt';

            $this->cachedStorage->put($path, 'content');

            $lastModified = $this->cachedStorage->lastModified($path);

            expect($lastModified)->toBeInt();
            expect($lastModified)->toBeLessThanOrEqual(time());
        });

        it('gets MIME type', function () {
            $path = 'file.txt';

            $this->cachedStorage->put($path, 'text content');

            $mimeType = $this->cachedStorage->getMimeType($path);

            expect($mimeType)->toContain('text');
        });

    });

    describe('Batch Operations', function () {
        it('puts multiple files with directory cache optimization', function () {
            $files = [
                'batch1/file1.txt' => 'content1',
                'batch1/file2.txt' => 'content2',
                'batch2/file3.txt' => 'content3',
            ];

            $this->cachedStorage->putMultiple($files);

            foreach ($files as $path => $content) {
                expect($this->cachedStorage->exists($path))->toBeTrue();
                expect($this->cachedStorage->get($path))->toBe($content);
            }

            $stats = $this->cachedStorage->getCacheStats();
            expect($stats['cached_directories'])->toBeGreaterThan(0);
        });

        it('gets multiple files', function () {
            $files = [
                'multi1.txt' => 'content1',
                'multi2.txt' => 'content2',
                'multi3.txt' => 'content3',
            ];

            // Write files
            $this->cachedStorage->putMultiple($files);

            // Read in batch
            $results = $this->cachedStorage->getMultiple(array_keys($files));

            expect($results)->toBe($files);
        });

        it('gets metadata for multiple files', function () {
            $files = [
                'meta1.txt' => 'content1',
                'meta2.txt' => 'content2',
            ];

            $this->cachedStorage->putMultiple($files);

            $metadata = $this->cachedStorage->getMetadataMultiple(array_keys($files));

            expect($metadata)->toHaveCount(2);
            expect($metadata['meta1.txt']->size)->toBe(strlen('content1'));
            expect($metadata['meta2.txt']->size)->toBe(strlen('content2'));
        });
    });

    describe('Directory Listing', function () {
        it('lists directory contents', function () {
            $dir = 'listable';

            $this->cachedStorage->put($dir . '/file1.txt', 'content1');
            $this->cachedStorage->put($dir . '/file2.txt', 'content2');
            $this->cachedStorage->put($dir . '/file3.txt', 'content3');

            $contents = $this->cachedStorage->listDirectory($dir);

            // listDirectory returns array with numeric keys and relative path values (including directory prefix)
            expect($contents)->toHaveCount(3);

            // Convert to simple array values for comparison
            $files = array_values($contents);
            expect($files)->toContain('listable/file1.txt');
            expect($files)->toContain('listable/file2.txt');
            expect($files)->toContain('listable/file3.txt');
        });
    });

    describe('Cache Management', function () {
        it('can clear directory cache', function () {
            $this->cachedStorage->put('cached/file.txt', 'content');

            $statsBefore = $this->cachedStorage->getCacheStats();
            expect($statsBefore['cached_directories'])->toBeGreaterThan(0);

            $this->cachedStorage->clearDirectoryCache();

            $statsAfter = $this->cachedStorage->getCacheStats();
            expect($statsAfter['cached_directories'])->toBe(0);
        });

        it('provides accurate cache statistics', function () {
            $this->cachedStorage->put('level1/level2/file.txt', 'content');

            $stats = $this->cachedStorage->getCacheStats();

            expect($stats)->toHaveKeys(['cached_directories', 'cache_entries']);
            expect($stats['cached_directories'])->toBeGreaterThan(0);
            expect($stats['cache_entries'])->toBeArray();
        });

        it('checks if directory is cached', function () {
            // Use relative path that will be resolved
            $relativePath = 'cached_check/file.txt';

            // Get the directory that will actually be cached
            $cachedDir = $this->testDir . '/cached_check';

            expect($this->cachedStorage->isDirectoryCached($cachedDir))->toBeFalse();

            $this->cachedStorage->put($relativePath, 'content');

            // After put, the directory should be cached
            expect($this->cachedStorage->isDirectoryCached($cachedDir))->toBeTrue();
        });
    });

    describe('Performance Characteristics', function () {
        it('reduces directory checks for repeated writes', function () {
            $dir = 'performance/test';

            // First write creates and caches directory
            $this->cachedStorage->put($dir . '/file1.txt', 'content1');

            $statsBefore = $this->cachedStorage->getCacheStats();

            // Subsequent writes reuse cached directory
            for ($i = 2; $i <= 10; $i++) {
                $this->cachedStorage->put($dir . "/file{$i}.txt", "content{$i}");
            }

            $statsAfter = $this->cachedStorage->getCacheStats();

            // Cache size should not grow significantly (same directory)
            expect($statsAfter['cached_directories'])->toBeLessThanOrEqual(
                $statsBefore['cached_directories'] + 1
            );
        });
    });
});
