<?php

declare(strict_types=1);

namespace Tests\Framework\Discovery;

use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheIdentifier;
use App\Framework\Cache\CacheItem;
use App\Framework\Cache\CacheResult;
use App\Framework\Cache\Driver\InMemoryCache;
use App\Framework\Core\PathProvider;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\DateTime\SystemClock;
use App\Framework\Discovery\UnifiedDiscoveryService;
use PHPUnit\Framework\TestCase;

/**
 * Simple Cache implementation for testing
 */
final class SimpleCacheWrapper implements Cache
{
    public function __construct(private InMemoryCache $driver)
    {
    }

    public function get(CacheIdentifier ...$identifiers): CacheResult
    {
        $keys = array_filter($identifiers, fn ($id) => $id instanceof \App\Framework\Cache\CacheKey);

        return $this->driver->get(...$keys);
    }

    public function set(CacheItem ...$items): bool
    {
        return $this->driver->set(...$items);
    }

    public function has(CacheIdentifier ...$identifiers): array
    {
        $keys = array_filter($identifiers, fn ($id) => $id instanceof \App\Framework\Cache\CacheKey);

        return $this->driver->has(...$keys);
    }

    public function forget(CacheIdentifier ...$identifiers): bool
    {
        $keys = array_filter($identifiers, fn ($id) => $id instanceof \App\Framework\Cache\CacheKey);

        return $this->driver->forget(...$keys);
    }

    public function clear(): bool
    {
        return $this->driver->clear();
    }

    public function remember(\App\Framework\Cache\CacheKey $key, callable $callback, ?Duration $ttl = null): CacheItem
    {
        $result = $this->driver->get($key);
        $item = $result->getItem($key);
        if ($item->isHit) {
            return $item;
        }

        $value = $callback();
        $newItem = CacheItem::forSet($key, $value, $ttl);
        $this->driver->set($newItem);

        return CacheItem::hit($key, $value);
    }
}

/**
 * Simplified memory test focusing only on Discovery core without container dependencies
 */
final class SimpleMemoryTest extends TestCase
{
    public function test_discovery_memory_usage_15_runs(): void
    {
        $iterations = 15;
        $memoryData = [];

        echo "\n=== Testing Discovery Memory Usage - $iterations Runs ===\n";

        // Create discovery service directly with mock dependencies
        $pathProvider = new PathProvider('/var/www/html');

        // Use InMemoryCache with wrapper to implement Cache interface
        $cacheDriver = new InMemoryCache();
        $cache = new SimpleCacheWrapper($cacheDriver);
        $clock = new SystemClock();

        // Create reflection provider without dependencies
        $reflectionProvider = new \App\Framework\Reflection\CachedReflectionProvider();

        $config = new \App\Framework\Discovery\ValueObjects\DiscoveryConfiguration(
            paths: ['/var/www/html/src'],
            attributeMappers: [
                new \App\Framework\Core\RouteMapper(),
                new \App\Framework\DI\InitializerMapper(),
            ],
            targetInterfaces: [],
            useCache: false
        );

        $discoveryService = new UnifiedDiscoveryService(
            pathProvider: $pathProvider,
            cache: $cache,
            clock: $clock,
            reflectionProvider: $reflectionProvider,
            configuration: $config,
            attributeMappers: [
                new \App\Framework\Core\RouteMapper(),
                new \App\Framework\DI\InitializerMapper(),
            ],
            targetInterfaces: []
        );

        for ($i = 1; $i <= $iterations; $i++) {
            echo "\n--- Discovery Run $i/$iterations ---\n";

            $memoryBefore = memory_get_usage(true);
            $peakBefore = memory_get_peak_usage(true);

            // Run discovery
            $registry = $discoveryService->discover();

            $memoryAfter = memory_get_usage(true);
            $peakAfter = memory_get_peak_usage(true);

            $memoryDiff = $memoryAfter - $memoryBefore;
            $peakDiff = $peakAfter - $peakBefore;

            $memoryData[$i] = [
                'memory_before' => $memoryBefore,
                'memory_after' => $memoryAfter,
                'memory_diff' => $memoryDiff,
                'peak_before' => $peakBefore,
                'peak_after' => $peakAfter,
                'peak_diff' => $peakDiff,
            ];

            echo sprintf(
                "Run %2d: Memory %s -> %s (diff: %s), Peak %s -> %s (diff: %s)\n",
                $i,
                $this->formatBytes($memoryBefore),
                $this->formatBytes($memoryAfter),
                $this->formatBytes($memoryDiff),
                $this->formatBytes($peakBefore),
                $this->formatBytes($peakAfter),
                $this->formatBytes($peakDiff)
            );

            // Log reflection stats every 5 runs
            if ($i % 5 === 0) {
                try {
                    $health = $discoveryService->getHealthStatus();
                    echo "  Health Status: " . json_encode($health) . "\n";
                } catch (\Throwable $e) {
                    echo "  Health Status: Error - " . $e->getMessage() . "\n";
                }
            }

            // Cleanup
            unset($registry);
            gc_collect_cycles();

            // Check for excessive memory usage
            if ($memoryDiff > 100 * 1024 * 1024) { // 100MB limit
                $this->fail("Run $i: Excessive memory usage: " . $this->formatBytes($memoryDiff));
            }
        }

        $this->analyzeResults($memoryData);
    }

    private function analyzeResults(array $memoryData): void
    {
        echo "\n=== Memory Analysis Results ===\n";

        $memoryDiffs = array_column($memoryData, 'memory_diff');
        $peakDiffs = array_column($memoryData, 'peak_diff');

        $avgMemory = array_sum($memoryDiffs) / count($memoryDiffs);
        $avgPeak = array_sum($peakDiffs) / count($peakDiffs);
        $maxMemory = max($memoryDiffs);
        $minMemory = min($memoryDiffs);
        $totalGrowth = array_sum($memoryDiffs);

        echo "Average memory per run: " . $this->formatBytes($avgMemory) . "\n";
        echo "Average peak per run: " . $this->formatBytes($avgPeak) . "\n";
        echo "Max memory per run: " . $this->formatBytes($maxMemory) . "\n";
        echo "Min memory per run: " . $this->formatBytes($minMemory) . "\n";
        echo "Total cumulative growth: " . $this->formatBytes($totalGrowth) . "\n";

        // Calculate growth trend
        $growthTrend = $this->calculateLinearTrend($memoryDiffs);
        echo "Growth trend (bytes per run): " . $this->formatBytes($growthTrend) . "\n";

        // Memory leak detection
        if ($growthTrend > 2 * 1024 * 1024) { // 2MB growth per run
            $this->fail("Detected memory leak: " . $this->formatBytes($growthTrend) . " growth per run");
        }

        if ($avgMemory > 50 * 1024 * 1024) { // 50MB average
            $this->fail("Average memory usage too high: " . $this->formatBytes($avgMemory));
        }

        // Check for stability - memory usage should be consistent
        $memoryVariance = $this->calculateVariance($memoryDiffs);
        echo "Memory usage variance: " . $this->formatBytes($memoryVariance) . "\n";

        if ($memoryVariance > 10 * 1024 * 1024) { // 10MB variance
            echo "WARNING: High memory usage variance detected\n";
        }

        echo "\n✅ Memory test completed successfully!\n";
        echo "✅ No significant memory leaks detected\n";
        echo "✅ Average memory usage within acceptable limits\n";
    }

    private function calculateLinearTrend(array $values): float
    {
        $n = count($values);
        if ($n < 3) {
            return 0;
        }

        $sumX = 0;
        $sumY = 0;
        $sumXY = 0;
        $sumX2 = 0;

        for ($i = 0; $i < $n; $i++) {
            $x = $i + 1;
            $y = $values[$i];
            $sumX += $x;
            $sumY += $y;
            $sumXY += $x * $y;
            $sumX2 += $x * $x;
        }

        return ($n * $sumXY - $sumX * $sumY) / ($n * $sumX2 - $sumX * $sumX);
    }

    private function calculateVariance(array $values): float
    {
        $mean = array_sum($values) / count($values);
        $squaredDiffs = array_map(fn ($value) => pow($value - $mean, 2), $values);

        return array_sum($squaredDiffs) / count($squaredDiffs);
    }

    private function formatBytes(int|float $bytes): string
    {
        if ($bytes < 0) {
            return '-' . $this->formatBytes(abs($bytes));
        }

        $units = ['B', 'KB', 'MB', 'GB'];
        $unitIndex = 0;

        while ($bytes >= 1024 && $unitIndex < count($units) - 1) {
            $bytes /= 1024;
            $unitIndex++;
        }

        return round($bytes, 1) . ' ' . $units[$unitIndex];
    }
}
