<?php

declare(strict_types=1);

use App\Framework\Performance\GarbageCollectionMonitor;
use App\Framework\Performance\ValueObjects\GcStatus;
use App\Framework\Performance\ValueObjects\GcResult;

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

    it('returns current GC status', function () {
        $status = $this->monitor->getStatus();

        expect($status)->toBeInstanceOf(GcStatus::class);
        expect($status->runs)->toBeInt();
        expect($status->collected)->toBeInt();
        expect($status->threshold)->toBeGreaterThan(0);
        expect($status->roots)->toBeInt();
    });

    it('gets number of GC runs', function () {
        $runs = $this->monitor->getRuns();

        expect($runs)->toBeInt();
        expect($runs)->toBeGreaterThanOrEqual(0);
    });

    it('gets number of collected cycles', function () {
        $collected = $this->monitor->getCollected();

        expect($collected)->toBeInt();
        expect($collected)->toBeGreaterThanOrEqual(0);
    });

    it('gets GC threshold', function () {
        $threshold = $this->monitor->getThreshold();

        expect($threshold)->toBeInt();
        expect($threshold)->toBeGreaterThan(0);
    });

    it('checks if GC is running', function () {
        $isRunning = $this->monitor->isRunning();

        expect($isRunning)->toBeBool();
    });

    it('checks if GC is enabled', function () {
        $isEnabled = $this->monitor->isEnabled();

        expect($isEnabled)->toBeBool();
    });

    it('can force garbage collection', function () {
        // Create some circular references to collect
        $obj1 = new stdClass();
        $obj2 = new stdClass();
        $obj1->ref = $obj2;
        $obj2->ref = $obj1;
        unset($obj1, $obj2);

        $result = $this->monitor->forceCollection();

        expect($result)->toBeInstanceOf(GcResult::class);
        expect($result->collected)->toBeInt();
        expect($result->duration)->toBeInstanceOf(App\Framework\Core\ValueObjects\Duration::class);
        expect($result->memoryFreed)->toBeInstanceOf(App\Framework\Core\ValueObjects\Byte::class);
        expect($result->statusBefore)->toBeInstanceOf(GcStatus::class);
        expect($result->statusAfter)->toBeInstanceOf(GcStatus::class);
        expect($result->timestamp)->toBeInstanceOf(App\Framework\Core\ValueObjects\Timestamp::class);
    });

    it('provides efficiency metrics', function () {
        $metrics = $this->monitor->getEfficiencyMetrics();

        expect($metrics)->toHaveKeys([
            'collection_rate',
            'threshold_ratio',
            'is_healthy',
            'runs',
            'collected',
            'roots',
            'threshold',
        ]);

        expect($metrics['collection_rate'])->toBeFloat();
        expect($metrics['threshold_ratio'])->toBeFloat();
        expect($metrics['is_healthy'])->toBeBool();
    });

    it('can take GC snapshot', function () {
        $snapshot = $this->monitor->takeSnapshot('test_checkpoint');

        expect($snapshot)->toHaveKeys(['label', 'timestamp', 'status', 'efficiency']);
        expect($snapshot['label'])->toBe('test_checkpoint');
        expect($snapshot['status'])->toBeArray();
        expect($snapshot['efficiency'])->toBeArray();
    });

    it('can enable and disable GC', function () {
        $this->monitor->disable();
        expect(gc_enabled())->toBeFalse();

        $this->monitor->enable();
        expect(gc_enabled())->toBeTrue();
    });
});

describe('GcStatus', function () {
    it('creates from gc_status array', function () {
        $status = GcStatus::fromGcStatus(gc_status());

        expect($status)->toBeInstanceOf(GcStatus::class);
        expect($status->runs)->toBeInt();
        expect($status->collected)->toBeInt();
        expect($status->threshold)->toBeInt();
        expect($status->roots)->toBeInt();
    });

    it('creates current GC status', function () {
        $status = GcStatus::current();

        expect($status)->toBeInstanceOf(GcStatus::class);
    });

    it('calculates collection efficiency', function () {
        $status = new GcStatus(runs: 10, collected: 50, threshold: 10001, roots: 100);

        $efficiency = $status->getCollectionEfficiency();

        expect($efficiency)->toBe(5.0); // 50 / 10
    });

    it('returns zero efficiency when no runs', function () {
        $status = new GcStatus(runs: 0, collected: 0, threshold: 10001, roots: 0);

        expect($status->getCollectionEfficiency())->toBe(0.0);
    });

    it('calculates threshold utilization', function () {
        $status = new GcStatus(runs: 10, collected: 50, threshold: 1000, roots: 900);

        $utilization = $status->getThresholdUtilization();

        expect($utilization)->toBe(0.9); // 900 / 1000
    });

    it('checks if near threshold', function () {
        $nearThreshold = new GcStatus(runs: 10, collected: 50, threshold: 1000, roots: 950);
        $notNearThreshold = new GcStatus(runs: 10, collected: 50, threshold: 1000, roots: 500);

        expect($nearThreshold->isNearThreshold(0.9))->toBeTrue();
        expect($notNearThreshold->isNearThreshold(0.9))->toBeFalse();
    });

    it('determines if GC is healthy', function () {
        $healthy = new GcStatus(runs: 10, collected: 50, threshold: 10000, roots: 100);
        $unhealthy = new GcStatus(runs: 0, collected: 0, threshold: 1000, roots: 950);

        expect($healthy->isHealthy())->toBeTrue();
        expect($unhealthy->isHealthy())->toBeFalse();
    });

    it('converts to array', function () {
        $status = new GcStatus(runs: 10, collected: 50, threshold: 10001, roots: 100);

        $array = $status->toArray();

        expect($array)->toHaveKeys([
            'runs',
            'collected',
            'threshold',
            'roots',
            'running',
            'collection_efficiency',
            'threshold_utilization',
            'is_healthy',
        ]);
    });
});

describe('GcResult', function () {
    it('determines if GC was effective', function () {
        $effective = new GcResult(
            collected: 10,
            duration: App\Framework\Core\ValueObjects\Duration::fromMilliseconds(5.0),
            memoryFreed: App\Framework\Core\ValueObjects\Byte::fromBytes(1024),
            statusBefore: new GcStatus(runs: 1, collected: 0, threshold: 10001, roots: 100),
            statusAfter: new GcStatus(runs: 2, collected: 10, threshold: 10001, roots: 90),
            timestamp: App\Framework\Core\ValueObjects\Timestamp::now()
        );

        $ineffective = new GcResult(
            collected: 0,
            duration: App\Framework\Core\ValueObjects\Duration::fromMilliseconds(5.0),
            memoryFreed: App\Framework\Core\ValueObjects\Byte::fromBytes(0),
            statusBefore: new GcStatus(runs: 1, collected: 0, threshold: 10001, roots: 100),
            statusAfter: new GcStatus(runs: 2, collected: 0, threshold: 10001, roots: 100),
            timestamp: App\Framework\Core\ValueObjects\Timestamp::now()
        );

        expect($effective->wasEffective())->toBeTrue();
        expect($ineffective->wasEffective())->toBeFalse();
    });

    it('gets GC overhead in milliseconds', function () {
        $result = new GcResult(
            collected: 5,
            duration: App\Framework\Core\ValueObjects\Duration::fromMilliseconds(12.5),
            memoryFreed: App\Framework\Core\ValueObjects\Byte::fromBytes(2048),
            statusBefore: new GcStatus(runs: 1, collected: 0, threshold: 10001, roots: 100),
            statusAfter: new GcStatus(runs: 2, collected: 5, threshold: 10001, roots: 95),
            timestamp: App\Framework\Core\ValueObjects\Timestamp::now()
        );

        expect($result->getOverheadMs())->toBeFloat();
        expect($result->getOverheadMs())->toBeGreaterThan(0);
    });

    it('gets memory freed in megabytes', function () {
        $result = new GcResult(
            collected: 5,
            duration: App\Framework\Core\ValueObjects\Duration::fromMilliseconds(5.0),
            memoryFreed: App\Framework\Core\ValueObjects\Byte::fromBytes(1048576), // 1 MB
            statusBefore: new GcStatus(runs: 1, collected: 0, threshold: 10001, roots: 100),
            statusAfter: new GcStatus(runs: 2, collected: 5, threshold: 10001, roots: 95),
            timestamp: App\Framework\Core\ValueObjects\Timestamp::now()
        );

        expect($result->getMemoryFreedMB())->toBe(1.0);
    });

    it('calculates efficiency score', function () {
        $result = new GcResult(
            collected: 5,
            duration: App\Framework\Core\ValueObjects\Duration::fromMilliseconds(5.0),
            memoryFreed: App\Framework\Core\ValueObjects\Byte::fromBytes(1048576),
            statusBefore: new GcStatus(runs: 1, collected: 0, threshold: 10001, roots: 100),
            statusAfter: new GcStatus(runs: 2, collected: 5, threshold: 10001, roots: 95),
            timestamp: App\Framework\Core\ValueObjects\Timestamp::now()
        );

        $score = $result->getEfficiencyScore();

        expect($score)->toBeFloat();
        expect($score)->toBeGreaterThanOrEqual(0);
        expect($score)->toBeLessThanOrEqual(100);
    });

    it('gets status changes', function () {
        $result = new GcResult(
            collected: 5,
            duration: App\Framework\Core\ValueObjects\Duration::fromMilliseconds(5.0),
            memoryFreed: App\Framework\Core\ValueObjects\Byte::fromBytes(1024),
            statusBefore: new GcStatus(runs: 1, collected: 10, threshold: 10001, roots: 100),
            statusAfter: new GcStatus(runs: 2, collected: 15, threshold: 10001, roots: 95),
            timestamp: App\Framework\Core\ValueObjects\Timestamp::now()
        );

        $changes = $result->getStatusChanges();

        expect($changes)->toHaveKeys(['runs_delta', 'collected_delta', 'roots_delta']);
        expect($changes['runs_delta'])->toBe(1);
        expect($changes['collected_delta'])->toBe(5);
        expect($changes['roots_delta'])->toBe(-5);
    });

    it('converts to array', function () {
        $result = new GcResult(
            collected: 5,
            duration: App\Framework\Core\ValueObjects\Duration::fromMilliseconds(5.0),
            memoryFreed: App\Framework\Core\ValueObjects\Byte::fromBytes(1024),
            statusBefore: new GcStatus(runs: 1, collected: 0, threshold: 10001, roots: 100),
            statusAfter: new GcStatus(runs: 2, collected: 5, threshold: 10001, roots: 95),
            timestamp: App\Framework\Core\ValueObjects\Timestamp::now()
        );

        $array = $result->toArray();

        expect($array)->toHaveKeys([
            'collected',
            'duration_ms',
            'memory_freed_bytes',
            'memory_freed_mb',
            'timestamp',
            'was_effective',
            'efficiency_score',
            'status_before',
            'status_after',
            'status_changes',
        ]);
    });
});
