<?php

declare(strict_types=1);

namespace App\Framework\Discovery\Testing;

use App\Framework\Cache\Cache;
use App\Framework\Core\PathProvider;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\DateTime\Clock;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Discovery\UnifiedDiscoveryService;
use App\Framework\Discovery\ValueObjects\DiscoveryConfiguration;
use App\Framework\Discovery\ValueObjects\DiscoveryContext;
use App\Framework\Discovery\ValueObjects\DiscoveryOptions;
use App\Framework\Discovery\ValueObjects\ScanType;
use App\Framework\Logging\Logger;
use App\Framework\Reflection\ReflectionProvider;

/**
 * Testing utilities for Discovery system
 *
 * Provides helper methods for testing Discovery components
 * with mock data, performance validation, and quality checks.
 */
final readonly class DiscoveryTestHelper
{
    public function __construct(
        private Clock   $clock,
        private ?Logger $logger = null
    ) {
    }

    /**
     * Create test discovery service with mocked dependencies
     */
    /**
     * Create test discovery service with mocked dependencies
     * @param array<string, mixed> $options
     */
    public function createTestDiscoveryService(array $options = []): UnifiedDiscoveryService
    {
        $pathProvider = $this->createMockPathProvider($options['base_path'] ?? '/tmp/test');
        $cache = $this->createMockCache($options['cache_enabled'] ?? true);
        $reflectionProvider = $this->createMockReflectionProvider();
        $configuration = $this->createTestConfiguration($options);

        return new UnifiedDiscoveryService(
            pathProvider: $pathProvider,
            cache: $cache,
            clock: $this->clock,
            reflectionProvider: $reflectionProvider,
            configuration: $configuration,
            attributeMappers: $options['attribute_mappers'] ?? [],
            targetInterfaces: $options['target_interfaces'] ?? [],
            logger: $this->logger
        );
    }

    /**
     * Create test discovery configuration
     */
    /**
     * Create test discovery configuration
     * @param array<string, mixed> $overrides
     */
    public function createTestConfiguration(array $overrides = []): DiscoveryConfiguration
    {
        return new DiscoveryConfiguration(
            paths                    : $overrides['paths'] ?? ['/tmp/test/src'],
            useCache                 : $overrides['use_cache'] ?? true,
            cacheTimeout             : Duration::fromHours($overrides['cache_timeout_hours'] ?? 1),
            memoryLimitMB            : $overrides['memory_limit_mb'] ?? 128,
            enableMemoryMonitoring   : $overrides['enable_memory_monitoring'] ?? true,
            enablePerformanceTracking: $overrides['enable_performance_tracking'] ?? true,
            maxFilesPerBatch         : $overrides['max_files_per_batch'] ?? 50,
            memoryPressureThreshold  : $overrides['memory_pressure_threshold'] ?? 0.8
        );
    }

    /**
     * Create test discovery context
     */
    public function createTestContext(array $options = []): DiscoveryContext
    {
        return new DiscoveryContext(
            paths: $options['paths'] ?? ['/tmp/test'],
            scanType: $options['scan_type'] ?? ScanType::FULL,
            options: $options['discovery_options'] ?? new DiscoveryOptions(),
            startTime: $options['start_time'] ?? $this->clock->now(),
            executionContext: $options['execution_context'] ?? null
        );
    }

    /**
     * Create test discovery options
     */
    public function createTestOptions(array $overrides = []): DiscoveryOptions
    {
        return new DiscoveryOptions(
            scanType: $overrides['scan_type'] ?? ScanType::FULL,
            paths: $overrides['paths'] ?? ['/tmp/test'],
            useCache: $overrides['use_cache'] ?? false
        );
    }

    /**
     * Create test directory structure with PHP files
     */
    public function createTestFileStructure(string $basePath, array $structure = []): array
    {
        $defaultStructure = [
            'src/Controllers/TestController.php' => $this->generateTestControllerContent(),
            'src/Services/TestService.php' => $this->generateTestServiceContent(),
            'src/Models/TestModel.php' => $this->generateTestModelContent(),
            'src/Interfaces/TestInterface.php' => $this->generateTestInterfaceContent(),
            'tests/TestCase.php' => $this->generateTestCaseContent(),
        ];

        $structure = array_merge($defaultStructure, $structure);
        $createdFiles = [];

        foreach ($structure as $relativePath => $content) {
            $fullPath = $basePath . '/' . $relativePath;
            $directory = dirname($fullPath);

            // Create directory structure
            if (! is_dir($directory)) {
                mkdir($directory, 0755, recursive: true);
            }

            // Create file
            file_put_contents($fullPath, $content);
            $createdFiles[] = $fullPath;
        }

        return $createdFiles;
    }

    /**
     * Clean up test directory structure
     */
    public function cleanupTestFileStructure(string $basePath): void
    {
        if (is_dir($basePath) && str_contains($basePath, '/tmp/')) {
            $this->removeDirectory($basePath);
        }
    }

    /**
     * Validate discovery results
     */
    public function validateDiscoveryResults(DiscoveryRegistry $registry, array $expectations = []): array
    {
        $validation = [
            'valid' => true,
            'errors' => [],
            'warnings' => [],
            'stats' => [
                'total_items' => count($registry),
                'routes_found' => 0,
                'attributes_found' => 0,
                'interfaces_found' => 0,
            ],
        ];

        // Count different types of discoveries
        foreach ($registry as $item) {
            if (str_contains(get_class($item), 'Route')) {
                $validation['stats']['routes_found']++;
            } elseif (str_contains(get_class($item), 'Attribute')) {
                $validation['stats']['attributes_found']++;
            } elseif (str_contains(get_class($item), 'Interface')) {
                $validation['stats']['interfaces_found']++;
            }
        }

        // Validate expectations
        if (isset($expectations['min_total_items'])) {
            if ($validation['stats']['total_items'] < $expectations['min_total_items']) {
                $validation['errors'][] = "Expected at least {$expectations['min_total_items']} items, found {$validation['stats']['total_items']}";
                $validation['valid'] = false;
            }
        }

        if (isset($expectations['max_total_items'])) {
            if ($validation['stats']['total_items'] > $expectations['max_total_items']) {
                $validation['warnings'][] = "Found {$validation['stats']['total_items']} items, expected max {$expectations['max_total_items']}";
            }
        }

        return $validation;
    }

    /**
     * Benchmark discovery performance
     */
    public function benchmarkDiscovery(UnifiedDiscoveryService $service, DiscoveryOptions $options, int $iterations = 3): array
    {
        $results = [
            'iterations' => $iterations,
            'times' => [],
            'memory_usage' => [],
            'cache_hits' => [],
            'statistics' => [],
        ];

        for ($i = 0; $i < $iterations; $i++) {
            $startTime = microtime(true);
            $startMemory = memory_get_usage(true);

            try {
                $registry = $service->discoverWithOptions($options);

                $endTime = microtime(true);
                $endMemory = memory_get_usage(true);

                $results['times'][] = $endTime - $startTime;
                $results['memory_usage'][] = $endMemory - $startMemory;
                $results['cache_hits'][] = count($registry);

            } catch (\Throwable $e) {
                $results['errors'][] = [
                    'iteration' => $i + 1,
                    'error' => $e->getMessage(),
                    'type' => get_class($e),
                ];
            }
        }

        // Calculate statistics
        if (! empty($results['times'])) {
            $results['statistics'] = [
                'avg_time' => array_sum($results['times']) / count($results['times']),
                'min_time' => min($results['times']),
                'max_time' => max($results['times']),
                'avg_memory' => array_sum($results['memory_usage']) / count($results['memory_usage']),
                'peak_memory' => max($results['memory_usage']),
                'total_time' => array_sum($results['times']),
            ];
        }

        return $results;
    }

    /**
     * Test memory behavior under pressure
     */
    public function testMemoryBehavior(UnifiedDiscoveryService $service, array $options = []): array
    {
        $memoryLimit = $options['memory_limit_mb'] ?? 64;
        $testFiles = $options['test_files'] ?? 100;

        // Create large test structure
        $basePath = sys_get_temp_dir() . '/discovery_memory_test_' . uniqid();
        $this->createLargeTestStructure($basePath, $testFiles);

        try {
            $config = $this->createTestConfiguration([
                'paths' => [$basePath],
                'memory_limit_mb' => $memoryLimit,
                'enable_memory_monitoring' => true,
            ]);

            $context = $this->createTestContext(['paths' => [$basePath]]);
            $startMemory = memory_get_usage(true);
            $peakMemory = $startMemory;

            // Monitor memory during discovery
            $registry = $service->discoverWithOptions(new DiscoveryOptions(
                scanType: ScanType::FULL,
                paths: [$basePath],
                useCache: false
            ));

            $endMemory = memory_get_usage(true);
            $peakMemory = memory_get_peak_usage(true);

            return [
                'test_successful' => true,
                'memory_stats' => [
                    'start_memory_mb' => round($startMemory / 1024 / 1024, 2),
                    'end_memory_mb' => round($endMemory / 1024 / 1024, 2),
                    'peak_memory_mb' => round($peakMemory / 1024 / 1024, 2),
                    'memory_limit_mb' => $memoryLimit,
                    'memory_used_percentage' => round(($peakMemory / ($memoryLimit * 1024 * 1024)) * 100, 1),
                ],
                'discovery_stats' => [
                    'items_found' => count($registry),
                    'test_files_created' => $testFiles,
                ],
            ];

        } catch (\Throwable $e) {
            return [
                'test_successful' => false,
                'error' => $e->getMessage(),
                'error_type' => get_class($e),
            ];
        } finally {
            $this->cleanupTestFileStructure($basePath);
        }
    }

    /**
     * Test concurrent discovery operations
     */
    public function testConcurrentDiscovery(UnifiedDiscoveryService $service, int $concurrentOperations = 3): array
    {
        $basePath = sys_get_temp_dir() . '/discovery_concurrent_test_' . uniqid();
        $this->createTestFileStructure($basePath);

        $results = [
            'concurrent_operations' => $concurrentOperations,
            'results' => [],
            'conflicts' => 0,
            'successful' => 0,
            'failed' => 0,
        ];

        try {
            $processes = [];

            for ($i = 0; $i < $concurrentOperations; $i++) {
                $options = new DiscoveryOptions(
                    scanType: ScanType::FULL,
                    paths: [$basePath],
                    useCache: true
                );

                try {
                    $startTime = microtime(true);
                    $registry = $service->discoverWithOptions($options);
                    $endTime = microtime(true);

                    $results['results'][] = [
                        'operation_id' => $i + 1,
                        'successful' => true,
                        'duration' => $endTime - $startTime,
                        'items_found' => count($registry),
                    ];

                    $results['successful']++;

                } catch (\Throwable $e) {
                    $results['results'][] = [
                        'operation_id' => $i + 1,
                        'successful' => false,
                        'error' => $e->getMessage(),
                        'error_type' => get_class($e),
                    ];

                    if (str_contains($e->getMessage(), 'concurrent')) {
                        $results['conflicts']++;
                    }

                    $results['failed']++;
                }
            }

        } finally {
            $this->cleanupTestFileStructure($basePath);
        }

        return $results;
    }

    /**
     * Generate test controller content
     */
    private function generateTestControllerContent(): string
    {
        return '<?php

declare(strict_types=1);

namespace Test\Controllers;

use App\Framework\Http\Attributes\Route;
use App\Framework\Http\Method;

final class TestController
{
    #[Route(path: \'/test\', method: Method::GET)]
    public function index(): string
    {
        return \'Test Controller\';
    }

    #[Route(path: \'/test/{id}\', method: Method::GET)]
    public function show(int $id): string
    {
        return "Test item: {$id}";
    }
}';
    }

    /**
     * Generate test service content
     */
    private function generateTestServiceContent(): string
    {
        return '<?php

declare(strict_types=1);

namespace Test\Services;

use App\Framework\Attributes\Singleton;

#[Singleton]
final class TestService
{
    public function process(string $data): string
    {
        return "Processed: {$data}";
    }
}';
    }

    /**
     * Generate test model content
     */
    private function generateTestModelContent(): string
    {
        return '<?php

declare(strict_types=1);

namespace Test\Models;

final readonly class TestModel
{
    public function __construct(
        public string $name,
        public int $value
    ) {
    }
}';
    }

    /**
     * Generate test interface content
     */
    private function generateTestInterfaceContent(): string
    {
        return '<?php

declare(strict_types=1);

namespace Test\Interfaces;

interface TestInterface
{
    public function execute(): mixed;
}';
    }

    /**
     * Generate test case content
     */
    private function generateTestCaseContent(): string
    {
        return '<?php

declare(strict_types=1);

namespace Test;

use PHPUnit\Framework\TestCase as BaseTestCase;

abstract class TestCase extends BaseTestCase
{
    protected function setUp(): void
    {
        parent::setUp();
    }
}';
    }

    /**
     * Create large test structure for memory testing
     */
    private function createLargeTestStructure(string $basePath, int $fileCount): void
    {
        $structure = [];

        for ($i = 0; $i < $fileCount; $i++) {
            $className = 'TestClass' . $i;
            $structure["src/Generated/{$className}.php"] = $this->generateLargeClassContent($className);
        }

        $this->createTestFileStructure($basePath, $structure);
    }

    /**
     * Generate large class content for memory testing
     */
    private function generateLargeClassContent(string $className): string
    {
        $methods = '';
        for ($i = 0; $i < 10; $i++) {
            $methods .= "
    public function method{$i}(): string
    {
        return 'Method {$i} result';
    }
";
        }

        return "<?php

declare(strict_types=1);

namespace Test\\Generated;

final class {$className}
{{$methods}
}";
    }

    /**
     * Create mock cache
     */
    private function createMockCache(bool $enabled): Cache
    {
        return new class ($enabled) implements Cache {
            public function __construct(private bool $enabled)
            {
            }

            public function get($key)
            {
                return $this->enabled ? null : null;
            }

            public function set($key, $value, $ttl = null): bool
            {
                return $this->enabled;
            }

            public function forget($key): bool
            {
                return true;
            }

            public function flush(): bool
            {
                return true;
            }
        };
    }

    /**
     * Create mock path provider
     */
    private function createMockPathProvider(string $basePath): PathProvider
    {
        return new class ($basePath) implements PathProvider {
            public function __construct(private string $basePath)
            {
            }

            public function getBasePath(): string
            {
                return $this->basePath;
            }
        };
    }

    /**
     * Create mock reflection provider
     */
    private function createMockReflectionProvider(): ReflectionProvider
    {
        return new class () implements ReflectionProvider {
            public function getClass(string $className): \ReflectionClass
            {
                return new \ReflectionClass($className);
            }
        };
    }

    /**
     * Remove directory recursively
     */
    private function removeDirectory(string $path): void
    {
        if (! is_dir($path)) {
            return;
        }

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

        foreach ($files as $file) {
            $fullPath = $path . '/' . $file;
            if (is_dir($fullPath)) {
                $this->removeDirectory($fullPath);
            } else {
                unlink($fullPath);
            }
        }

        rmdir($path);
    }
}
