import { Logger } from '@common/Logger';
import { CORE_PERF_THRESHOLDS, PerfMonitor } from '@common/PerfMonitor';
import {
    PERF_TOUCH_RESPONSE_MAX_MS,
    PERF_COMBO_RECOGNITION_MAX_MS,
    MAX_FIRST_PACKAGE_BYTES,
    MAX_AUDIO_BUNDLE_BYTES,
    MAX_MEMORY_PEAK_BYTES,
} from '@common/Constants';

describe('PerfMonitor — threshold catalog (req 18 & 20)', () => {
    it('includes every KPI threshold called out in requirements 20.1-20.5', () => {
        const names = CORE_PERF_THRESHOLDS.map((t) => t.metric);
        expect(names).toContain('input/touchStart');
        expect(names).toContain('jump/state_toggle_ms');
        expect(names).toContain('input/combo_recognition_ms');
        expect(names).toContain('input/parabolic_accuracy');
        expect(names).toContain('jump/air_jump_block_rate');
    });
});

describe('PerfMonitor — pass/fail evaluation', () => {
    function seedPassing(logger: Logger): void {
        for (let i = 0; i < 100; i++) {
            logger.metric({ name: 'input/touchStart', value: 20 });
            logger.metric({ name: 'jump/state_toggle_ms', value: 25 });
            logger.metric({ name: 'input/combo_recognition_ms', value: 40 });
        }
        logger.metric({ name: 'input/parabolic_accuracy', value: 0.97 });
        logger.metric({ name: 'jump/air_jump_block_rate', value: 0.995 });
    }

    it('passes when all metrics are within budget', () => {
        const logger = new Logger();
        seedPassing(logger);
        const monitor = new PerfMonitor(logger);
        const report = monitor.collectReport();
        expect(report.allPassing).toBe(true);
    });

    it('fails when no samples exist for a threshold', () => {
        const monitor = new PerfMonitor(new Logger());
        const report = monitor.collectReport();
        expect(report.allPassing).toBe(false);
        expect(report.checks[0].reason).toMatch(/no samples/);
    });

    it('fails when p95 latency exceeds the limit', () => {
        const logger = new Logger();
        seedPassing(logger);
        // Push a huge batch of slow touches so p95 exceeds budget.
        for (let i = 0; i < 200; i++) {
            logger.metric({ name: 'input/touchStart', value: PERF_TOUCH_RESPONSE_MAX_MS + 50 });
        }
        const report = new PerfMonitor(logger).collectReport();
        const touchCheck = report.checks.find((c) => c.threshold.metric === 'input/touchStart')!;
        expect(touchCheck.passing).toBe(false);
    });

    it('fails when parabolic accuracy drops below 95%', () => {
        const logger = new Logger();
        seedPassing(logger);
        logger.resetMetrics();
        logger.metric({ name: 'input/touchStart', value: 20 });
        logger.metric({ name: 'jump/state_toggle_ms', value: 20 });
        logger.metric({ name: 'input/combo_recognition_ms', value: 20 });
        logger.metric({ name: 'input/parabolic_accuracy', value: 0.8 });
        logger.metric({ name: 'jump/air_jump_block_rate', value: 0.999 });
        const report = new PerfMonitor(logger).collectReport();
        expect(report.allPassing).toBe(false);
    });

    it('evaluates build-size budget when provided', () => {
        const logger = new Logger();
        logger.metric({ name: 'input/touchStart', value: 10 });
        logger.metric({ name: 'jump/state_toggle_ms', value: 10 });
        logger.metric({ name: 'input/combo_recognition_ms', value: 10 });
        logger.metric({ name: 'input/parabolic_accuracy', value: 1 });
        logger.metric({ name: 'jump/air_jump_block_rate', value: 1 });
        const monitor = new PerfMonitor(logger);

        const okReport = monitor.collectReport({
            firstPackageBytes: MAX_FIRST_PACKAGE_BYTES - 1,
            audioBundleBytes: MAX_AUDIO_BUNDLE_BYTES - 1,
            memoryPeakBytes: MAX_MEMORY_PEAK_BYTES - 1,
        });
        expect(okReport.sizeBudgetPassing).toBe(true);
        expect(okReport.allPassing).toBe(true);

        const badReport = monitor.collectReport({
            firstPackageBytes: MAX_FIRST_PACKAGE_BYTES + 1,
            audioBundleBytes: MAX_AUDIO_BUNDLE_BYTES + 1,
            memoryPeakBytes: MAX_MEMORY_PEAK_BYTES + 1,
        });
        expect(badReport.sizeBudgetPassing).toBe(false);
        expect(badReport.allPassing).toBe(false);
    });

    it('honours the <= comparator at exactly the boundary', () => {
        const logger = new Logger();
        logger.metric({ name: 'input/touchStart', value: PERF_TOUCH_RESPONSE_MAX_MS });
        logger.metric({ name: 'jump/state_toggle_ms', value: 10 });
        logger.metric({ name: 'input/combo_recognition_ms', value: PERF_COMBO_RECOGNITION_MAX_MS });
        logger.metric({ name: 'input/parabolic_accuracy', value: 1 });
        logger.metric({ name: 'jump/air_jump_block_rate', value: 1 });
        const report = new PerfMonitor(logger).collectReport();
        const touch = report.checks.find((c) => c.threshold.metric === 'input/touchStart')!;
        expect(touch.passing).toBe(true);
    });
});
