import { Logger, MetricAggregate } from './Logger';
import {
    PERF_TOUCH_RESPONSE_MAX_MS,
    PERF_JUMP_STATE_TOGGLE_MAX_MS,
    PERF_COMBO_RECOGNITION_MAX_MS,
    PERF_PARABOLIC_ANGLE_ACCURACY_TARGET,
    PERF_AIR_JUMP_BLOCK_RATE_TARGET,
    MAX_FIRST_PACKAGE_BYTES,
    MAX_AUDIO_BUNDLE_BYTES,
    MAX_MEMORY_PEAK_BYTES,
} from './Constants';

/**
 * Performance monitor (task 10.2, req 18 & 20).
 *
 * Aggregates all the KPI samples recorded through `Logger.metric(...)` and
 * reports pass/fail against every threshold listed in the requirements doc.
 * CI can run `collectReport()` and assert that `allPassing === true`.
 */

export interface IPerfThreshold {
    metric: string;
    /** Budget target (max for latency, min for rates). */
    limit: number;
    comparator: '<=' | '>=';
    requirementId: string;
}

export const CORE_PERF_THRESHOLDS: ReadonlyArray<IPerfThreshold> = [
    { metric: 'input/touchStart', limit: PERF_TOUCH_RESPONSE_MAX_MS, comparator: '<=', requirementId: 'req 20.1' },
    { metric: 'jump/state_toggle_ms', limit: PERF_JUMP_STATE_TOGGLE_MAX_MS, comparator: '<=', requirementId: 'req 20.2' },
    { metric: 'input/parabolic_accuracy', limit: PERF_PARABOLIC_ANGLE_ACCURACY_TARGET, comparator: '>=', requirementId: 'req 20.3' },
    { metric: 'input/combo_recognition_ms', limit: PERF_COMBO_RECOGNITION_MAX_MS, comparator: '<=', requirementId: 'req 20.4' },
    { metric: 'jump/air_jump_block_rate', limit: PERF_AIR_JUMP_BLOCK_RATE_TARGET, comparator: '>=', requirementId: 'req 20.5' },
];

export interface IPerfCheckResult {
    threshold: IPerfThreshold;
    aggregate?: MetricAggregate;
    passing: boolean;
    reason: string;
}

export interface IPerfReport {
    allPassing: boolean;
    checks: IPerfCheckResult[];
    /** Optional build/runtime sizes filled in by CI (bytes). */
    firstPackageBytes?: number;
    audioBundleBytes?: number;
    memoryPeakBytes?: number;
    /** Top-level pass/fail for the size budgets. */
    sizeBudgetPassing?: boolean;
}

export class PerfMonitor {
    constructor(
        private readonly logger: Logger,
        private readonly thresholds: ReadonlyArray<IPerfThreshold> = CORE_PERF_THRESHOLDS
    ) {}

    public collectReport(buildSizes?: {
        firstPackageBytes?: number;
        audioBundleBytes?: number;
        memoryPeakBytes?: number;
    }): IPerfReport {
        const checks: IPerfCheckResult[] = this.thresholds.map((t) => this.check(t));
        let sizeBudgetPassing: boolean | undefined;
        if (buildSizes) {
            sizeBudgetPassing =
                (buildSizes.firstPackageBytes ?? 0) <= MAX_FIRST_PACKAGE_BYTES &&
                (buildSizes.audioBundleBytes ?? 0) <= MAX_AUDIO_BUNDLE_BYTES &&
                (buildSizes.memoryPeakBytes ?? 0) <= MAX_MEMORY_PEAK_BYTES;
        }
        const allPassing = checks.every((c) => c.passing) && (sizeBudgetPassing ?? true);
        return {
            allPassing,
            checks,
            firstPackageBytes: buildSizes?.firstPackageBytes,
            audioBundleBytes: buildSizes?.audioBundleBytes,
            memoryPeakBytes: buildSizes?.memoryPeakBytes,
            sizeBudgetPassing,
        };
    }

    private check(t: IPerfThreshold): IPerfCheckResult {
        const agg = this.logger.aggregate(t.metric);
        if (!agg) {
            return {
                threshold: t,
                passing: false,
                reason: `no samples recorded for "${t.metric}"`,
            };
        }
        // For latency, use p95. For rate metrics, use avg.
        const isRate = t.comparator === '>=';
        const observed = isRate ? agg.avg : agg.p95;
        const passing = isRate ? observed >= t.limit : observed <= t.limit;
        return {
            threshold: t,
            aggregate: agg,
            passing,
            reason: `${t.metric} ${isRate ? 'avg' : 'p95'}=${observed.toFixed(2)} vs limit ${t.limit} (${t.comparator})`,
        };
    }
}
