const ErrorMetrics = require('./errorMetrics');

// Dummy logger for tests
const logger = {
    error: console.error,
    debug: console.log,
    info: console.log
};

const config = {
    thresholds: {
        NRMSE_LOW: 0.05,
        NRMSE_MEDIUM: 0.10,
        NRMSE_HIGH: 0.15,
        LONG_TERM_LOW: 0.02,
        LONG_TERM_MEDIUM: 0.04,
        LONG_TERM_HIGH: 0.06
    }
};

class ErrorMetricsTester {
    constructor() {
        this.totalTests = 0;
        this.passedTests = 0;
        this.failedTests = 0;
        this.errorMetrics = new ErrorMetrics(config, logger);
    }

    assert(condition, message) {
        this.totalTests++;
        if (condition) {
            console.log(`✓ PASS: ${message}`);
            this.passedTests++;
        } else {
            console.log(`✗ FAIL: ${message}`);
            this.failedTests++;
        }
    }

    testMeanSquaredError() {
        console.log("\nTesting Mean Squared Error...");
        const predicted = [1, 2, 3];
        const measured = [1, 3, 5];
        const mse = this.errorMetrics.meanSquaredError(predicted, measured);
        this.assert(Math.abs(mse - 1.67) < 0.1, "MSE correctly calculated");
    }

    testRootMeanSquaredError() {
        console.log("\nTesting Root Mean Squared Error...");
        const predicted = [1, 2, 3];
        const measured = [1, 3, 5];
        const rmse = this.errorMetrics.rootMeanSquaredError(predicted, measured);
        this.assert(Math.abs(rmse - 1.29) < 0.1, "RMSE correctly calculated");
    }

    testNormalizedRMSE() {
        console.log("\nTesting Normalized RMSE...");
        const predicted = [100, 102, 104];
        const measured = [98, 103, 107];
        const processMin = 90, processMax = 110;
        const nrmse = this.errorMetrics.normalizedRootMeanSquaredError(predicted, measured, processMin, processMax);
        this.assert(typeof nrmse === 'number' && nrmse > 0, "Normalized RMSE calculated correctly");
    }

    testNormalizeUsingRealtime() {
        console.log("\nTesting Normalize Using Realtime...");
        const predicted = [100, 102, 104];
        const measured = [98, 103, 107];
        
        try {
            const nrmse = this.errorMetrics.normalizeUsingRealtime(predicted, measured);
            this.assert(typeof nrmse === 'number' && nrmse > 0, "Normalize using realtime calculated correctly");
        } catch (error) {
            this.assert(false, `Normalize using realtime failed: ${error.message}`);
        }
        
        // Test with identical values to check error handling
        const sameValues = [100, 100, 100];
        try {
            this.errorMetrics.normalizeUsingRealtime(sameValues, sameValues);
            this.assert(false, "Should throw error with identical values");
        } catch (error) {
            this.assert(true, "Correctly throws error when min/max are the same");
        }
    }

    testLongTermNRMSD() {
        console.log("\nTesting Long Term NRMSD Accumulation...");
        // Reset the accumulation values
        this.errorMetrics.cumNRMSD = 0;
        this.errorMetrics.cumCount = 0;
        
        let lastValue = 0;
        for (let i = 0; i < 100; i++) {
            lastValue = this.errorMetrics.longTermNRMSD(0.1 + i * 0.001);
        }
        
        this.assert(
            this.errorMetrics.cumCount === 100 && 
            this.errorMetrics.cumNRMSD !== 0 &&
            lastValue !== 0, 
            "Long term NRMSD accumulates over 100 iterations"
        );
        
        // Test that values are returned only after accumulating 100 samples
        this.errorMetrics.cumNRMSD = 0;
        this.errorMetrics.cumCount = 0;
        
        for (let i = 0; i < 99; i++) {
            const result = this.errorMetrics.longTermNRMSD(0.1);
            this.assert(result === 0, "No longTermNRMSD returned before 100 samples");
        }
        
        // Use a different value for the 100th sample to ensure a non-zero result
        const result = this.errorMetrics.longTermNRMSD(0.2);
        this.assert(result !== 0, "longTermNRMSD returned after 100 samples");
    }

    testDetectImmediateDrift() {
        console.log("\nTesting Immediate Drift Detection...");
        
        // Test high drift
        let drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_HIGH + 0.01);
        this.assert(drift.level === 3, "Detects high immediate drift correctly");
        
        // Test medium drift
        drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_MEDIUM + 0.01);
        this.assert(drift.level === 2, "Detects medium immediate drift correctly");
        
        // Test low drift
        drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_LOW + 0.01);
        this.assert(drift.level === 1, "Detects low immediate drift correctly");
        
        // Test no drift
        drift = this.errorMetrics.detectImmediateDrift(config.thresholds.NRMSE_LOW - 0.01);
        this.assert(drift.level === 0, "Detects no immediate drift correctly");
    }

    testDetectLongTermDrift() {
        console.log("\nTesting Long Term Drift Detection...");
        
        // Test high drift
        let drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_HIGH + 0.01);
        this.assert(drift.level === 3, "Detects high long-term drift correctly");
        
        // Test medium drift
        drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_MEDIUM + 0.01);
        this.assert(drift.level === 2, "Detects medium long-term drift correctly");
        
        // Test low drift
        drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_LOW + 0.01);
        this.assert(drift.level === 1, "Detects low long-term drift correctly");
        
        // Test no drift
        drift = this.errorMetrics.detectLongTermDrift(config.thresholds.LONG_TERM_LOW - 0.01);
        this.assert(drift.level === 0, "Detects no long-term drift correctly");
        
        // Test negative drift values
        drift = this.errorMetrics.detectLongTermDrift(-config.thresholds.LONG_TERM_HIGH - 0.01);
        this.assert(drift.level === 3, "Detects negative high long-term drift correctly");
    }

    testDriftDetection() {
        console.log("\nTesting Combined Drift Detection...");
        
        let nrmseHigh = config.thresholds.NRMSE_HIGH + 0.01;
        let ltNRMSD = 0;
        
        let result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD);
        
        this.assert(
            result !== null && 
            result.ImmDrift && 
            result.ImmDrift.level === 3 &&
            result.LongTermDrift.level === 0, 
            "Detects high immediate drift with no long-term drift"
        );

        nrmseHigh = config.thresholds.NRMSE_LOW - 0.01;
        ltNRMSD = config.thresholds.LONG_TERM_MEDIUM + 0.01;
        result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD);
        this.assert(
            result !== null && 
            result.ImmDrift.level === 0 &&
            result.LongTermDrift && 
            result.LongTermDrift.level === 2, 
            "Detects medium long-term drift with no immediate drift"
        );

        nrmseHigh = config.thresholds.NRMSE_MEDIUM + 0.01;
        ltNRMSD = config.thresholds.LONG_TERM_MEDIUM + 0.01;
        result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD);
        this.assert(
            result.ImmDrift.level === 2 && 
            result.LongTermDrift.level === 2, 
            "Detects both medium immediate and medium long-term drift"
        );

        nrmseHigh = config.thresholds.NRMSE_LOW - 0.01;
        ltNRMSD = config.thresholds.LONG_TERM_LOW - 0.01;
        result = this.errorMetrics.detectDrift(nrmseHigh, ltNRMSD);
        this.assert(
            result.ImmDrift.level === 0 && 
            result.LongTermDrift.level === 0, 
            "No significant drift detected when under thresholds"
        );
    }

    testAssessDrift() {
        console.log("\nTesting assessDrift function...");
        
        // Reset accumulation for testing
        this.errorMetrics.cumNRMSD = 0;
        this.errorMetrics.cumCount = 0;
        
        const predicted = [100, 101, 102, 103];
        const measured = [90, 91, 92, 93]; 
        const processMin = 90, processMax = 110;
        
        let result = this.errorMetrics.assessDrift(predicted, measured, processMin, processMax);
        
        this.assert(
            result !== null && 
            typeof result.nrmse === 'number' &&
            typeof result.longTermNRMSD === 'number' &&
            typeof result.immediateLevel === 'number' &&
            typeof result.immediateFeedback === 'string' &&
            typeof result.longTermLevel === 'number' &&
            typeof result.longTermFeedback === 'string',
            "assessDrift returns complete result structure"
        );

        this.assert(
            result.immediateLevel > 0,
            "assessDrift detects immediate drift with significant difference"
        );

        // Test with identical values
        result = this.errorMetrics.assessDrift(predicted, predicted, processMin, processMax);
        this.assert(
            result.nrmse === 0 &&
            result.immediateLevel === 0, 
            "assessDrift indicates no immediate drift when predicted equals measured"
        );

        // Test with slight drift 
        const measuredSlight = [100, 100.5, 101, 101.5];
        result = this.errorMetrics.assessDrift(predicted, measuredSlight, processMin, processMax);
        
        this.assert(
            result !== null && 
            result.nrmse < 0.05 &&
            (result.immediateLevel < 2),
            "assessDrift returns appropriate levels for slight drift"
        );

        // Test long-term drift accumulation
        for (let i = 0; i < 100; i++) {
            this.errorMetrics.assessDrift(
                predicted,
                measured.map(m => m + (Math.random() * 2 - 1)), // Add small random fluctuation
                processMin,
                processMax
            );
        }
        
        result = this.errorMetrics.assessDrift(predicted, measured, processMin, processMax);
        this.assert(
            result.longTermNRMSD !== 0,
            "Long-term drift accumulates over multiple assessments"
        );
    }

    async runAllTests() {
        console.log("\nStarting Error Metrics Tests...\n");
        this.testMeanSquaredError();
        this.testRootMeanSquaredError();
        this.testNormalizedRMSE();
        this.testNormalizeUsingRealtime();
        this.testLongTermNRMSD();
        this.testDetectImmediateDrift();
        this.testDetectLongTermDrift();
        this.testDriftDetection();
        this.testAssessDrift();
        
        console.log("\nTest Summary:");
        console.log(`Total Tests: ${this.totalTests}`);
        console.log(`Passed: ${this.passedTests}`);
        console.log(`Failed: ${this.failedTests}`);

        process.exit(this.failedTests > 0 ? 1 : 0);
    }
}

// Run all tests
const tester = new ErrorMetricsTester();
tester.runAllTests().catch(console.error);
