<?php

declare(strict_types=1);

namespace App\Framework\Discovery;

use App\Framework\Core\ValueObjects\Byte;
use App\Framework\Core\ValueObjects\GrowthRate;
use App\Framework\Core\ValueObjects\Percentage;
use App\Framework\DateTime\Clock;
use App\Framework\Discovery\ValueObjects\MemoryLeakInfo;
use App\Framework\Logging\Logger;
use App\Framework\Performance\MemoryMonitor;

/**
 * Advanced memory leak prevention and detection system
 */
final class MemoryGuard
{
    /** @var array<string, int> Tracking memory usage per operation */
    private array $memoryCheckpoints = [];

    /** @var array<string, MemoryLeakInfo> Detected memory leaks */
    private array $detectedLeaks = [];

    /** @var int Maximum allowed memory growth per operation (5MB) */
    private const int MAX_MEMORY_GROWTH = 5 * 1024 * 1024;

    /** @var float Memory leak detection threshold (50% growth) */
    private const float LEAK_THRESHOLD = 1.5;

    public function __construct(
        private readonly MemoryMonitor $memoryMonitor,
        private readonly Clock $clock,
        private readonly ?Logger $logger = null
    ) {
    }

    /**
     * Create a memory checkpoint before an operation
     */
    public function checkpoint(string $operation): void
    {
        // Force garbage collection before checkpoint
        $this->forceCleanup();

        $currentMemory = $this->memoryMonitor->getCurrentMemory()->toBytes();
        $this->memoryCheckpoints[$operation] = $currentMemory;

        $this->logger?->debug("Memory checkpoint created for {$operation}: {$currentMemory} bytes");
    }

    /**
     * Validate memory usage after an operation
     * @return bool True if memory usage is acceptable, false if potential leak detected
     */
    public function validate(string $operation): bool
    {
        if (! isset($this->memoryCheckpoints[$operation])) {
            $this->logger?->warning("No checkpoint found for operation: {$operation}");

            return true;
        }

        // Force cleanup before measuring
        $this->forceCleanup();

        $startMemory = $this->memoryCheckpoints[$operation];
        $currentMemory = $this->memoryMonitor->getCurrentMemory()->toBytes();
        $memoryGrowth = $currentMemory - $startMemory;

        // Remove checkpoint
        unset($this->memoryCheckpoints[$operation]);

        // Check for excessive memory growth
        if ($memoryGrowth > self::MAX_MEMORY_GROWTH) {
            $leakInfo = new MemoryLeakInfo(
                operation: $operation,
                startMemory: Byte::fromBytes($startMemory),
                endMemory: Byte::fromBytes($currentMemory),
                growth: Byte::fromBytes($memoryGrowth),
                growthPercentage: GrowthRate::fromMemoryValues($currentMemory, $startMemory),
                timestamp: $this->clock->time()
            );

            $this->detectedLeaks[$operation] = $leakInfo;

            $this->logger?->error("Potential memory leak detected in {$operation}: {$leakInfo}");

            // Attempt aggressive cleanup
            $this->aggressiveCleanup();

            return false;
        }

        // Check for percentage-based leak
        if ($startMemory > 0 && $currentMemory > $startMemory * self::LEAK_THRESHOLD) {
            $growthPercentage = ($currentMemory / $startMemory - 1) * 100;
            $this->logger?->warning(
                "High memory growth detected in {$operation}: {$growthPercentage}% increase"
            );
        }

        return true;
    }

    /**
     * Execute a callback with memory protection
     * @template T
     * @param callable(): T $callback
     * @return T
     */
    public function protect(string $operation, callable $callback): mixed
    {
        $this->checkpoint($operation);

        try {
            $result = $callback();

            if (! $this->validate($operation)) {
                $this->logger?->warning("Memory leak detected after {$operation}");
            }

            return $result;
        } catch (\Throwable $e) {
            // Clean up checkpoint on error
            unset($this->memoryCheckpoints[$operation]);

            throw $e;
        }
    }

    /**
     * Process data in chunks with memory protection
     * @template T
     * @param array<T> $items
     * @param callable(T): void $processor
     */
    public function processWithMemoryGuard(array $items, callable $processor, int $chunkSize = 100): void
    {
        $chunks = array_chunk($items, $chunkSize);
        $chunkNumber = 0;

        foreach ($chunks as $chunk) {
            $chunkNumber++;
            $operation = "chunk_{$chunkNumber}";

            $this->protect($operation, function () use ($chunk, $processor) {
                foreach ($chunk as $item) {
                    $processor($item);
                }
            });

            // Check if we're approaching memory limits
            if ($this->isMemoryPressureHigh()) {
                $this->logger?->warning("High memory pressure detected, forcing cleanup");
                $this->aggressiveCleanup();

                // Reduce chunk size for next iteration
                $chunkSize = max(10, (int)($chunkSize * 0.5));
            }
        }
    }

    /**
     * Force garbage collection and cleanup
     */
    public function forceCleanup(): void
    {
        // Multiple GC runs for thorough cleanup
        gc_collect_cycles();
        gc_collect_cycles();

        // PHP 7.3+ memory cache cleanup
        if (function_exists('gc_mem_caches')) {
            gc_mem_caches();
        }
    }

    /**
     * Aggressive memory cleanup for critical situations
     */
    public function aggressiveCleanup(): void
    {
        // Clear all internal caches
        $this->clearInternalCaches();

        // Force multiple GC runs
        for ($i = 0; $i < 3; $i++) {
            gc_collect_cycles();
            usleep(10000); // 10ms pause between runs
        }

        // Clear opcache if available
        if (function_exists('opcache_reset')) {
            opcache_reset();
        }

        // Clear realpath cache
        clearstatcache(true);

        $this->logger?->info("Aggressive memory cleanup completed");
    }

    /**
     * Check if memory pressure is high
     */
    public function isMemoryPressureHigh(): bool
    {
        $usage = $this->memoryMonitor->getMemoryUsagePercentage();

        return $usage->greaterThan(Percentage::from(80.0));
    }

    /**
     * Get detected memory leaks
     * @return array<string, MemoryLeakInfo>
     */
    public function getDetectedLeaks(): array
    {
        return $this->detectedLeaks;
    }

    /**
     * Clear detected leaks history
     */
    public function clearLeakHistory(): void
    {
        $this->detectedLeaks = [];
    }

    /**
     * Get memory guard statistics
     */
    public function getStatistics(): array
    {
        return [
            'active_checkpoints' => count($this->memoryCheckpoints),
            'detected_leaks' => count($this->detectedLeaks),
            'current_memory' => $this->memoryMonitor->getCurrentMemory()->toHumanReadable(),
            'peak_memory' => $this->memoryMonitor->getPeakMemory()->toHumanReadable(),
            'memory_usage' => $this->memoryMonitor->getMemoryUsagePercentage()->getValue() . '%',
            'is_high_pressure' => $this->isMemoryPressureHigh(),
        ];
    }

    /**
     * Clear internal caches (to be extended by specific implementations)
     */
    private function clearInternalCaches(): void
    {
        // Clear any static caches
        // This is a hook for clearing application-specific caches
    }
}
