//@ts-nocheck
import { test, expect, suite, it } from 'vitest';
import { 
    MultipleCounter,
    QuantileArray,
    TimeCounter,
    estimateArrayJsonSize,
    isWithinMargin,
} from './statsUtils';


suite('MultipleCounter', () => {
    it('should instantiate empty correctly', () => {
        const counter = new MultipleCounter();
        expect(counter).toBeInstanceOf(MultipleCounter);
        expect(counter.toArray()).toEqual([]);
        expect(counter.toJSON()).toEqual({});
    });

    it('should instantiate locked if specified', () => {
        const lockedCounter = new MultipleCounter(undefined, true);
        expect(() => lockedCounter.count('a')).toThrowError('is locked');
        expect(() => lockedCounter.clear()).toThrowError('is locked');
    });

    it('should handle instantiation data error', () => {
        expect(() => new MultipleCounter({ a: 'b' as any })).toThrowError('only integer');
    });

    const counterWithData = new MultipleCounter({ a: 1, b: 2 });
    it('should instantiate with object correctly', () => {
        expect(counterWithData.toArray()).toEqual([['a', 1], ['b', 2]]);
        expect(counterWithData.toJSON()).toEqual({ a: 1, b: 2 });
    });
    it('should count and clear', () => {
        counterWithData.count('a');
        expect(counterWithData.toJSON()).toEqual({ a: 2, b: 2 });
        counterWithData.count('b');
        counterWithData.count('c', 5);
        expect(counterWithData.toJSON()).toEqual({ a: 2, b: 3, c: 5 });
        counterWithData.clear();
        expect(counterWithData.toJSON()).toEqual({});
    });

    it('should sort the data', () => {
        const counter = new MultipleCounter({ a: 3, z: 1, c: 2 });
        expect(counter.toSortedKeyObject()).toEqual({ a: 3, c: 2, z: 1 });
        expect(counter.toSortedKeyObject(true)).toEqual({ z: 1, c: 2, a: 3 });
        expect(counter.toSortedValuesObject()).toEqual({ a: 3, c: 2, z: 1 });
        expect(counter.toSortedValuesObject(true)).toEqual({ z: 1, c: 2, a: 3 });
        expect(counter.toSortedKeysArray()).toEqual([['a', 3], ['c', 2], ['z', 1]]);
        expect(counter.toSortedKeysArray(true)).toEqual([['z', 1], ['c', 2], ['a', 3]]);
        expect(counter.toSortedValuesArray()).toEqual([['z', 1], ['c', 2], ['a', 3]]);
        expect(counter.toSortedValuesArray(true)).toEqual([['a', 3], ['c', 2], ['z', 1]]);
    });

    suite('should handle merging counters', () => {
        it('with another counter', () => {
            const ogCounter = new MultipleCounter({ a: 1, b: 2 });
            const newCounter = new MultipleCounter({ b: 3, c: 4 });
            ogCounter.merge(newCounter);
            expect(ogCounter.toJSON()).toEqual({ a: 1, b: 5, c: 4 });
        });
        it('with an array', () => {
            const ogCounter = new MultipleCounter({ a: 1, b: 2 });
            ogCounter.merge([['b', 3], ['c', 4]]);
            expect(ogCounter.toJSON()).toEqual({ a: 1, b: 5, c: 4 });
        });
        it('with an object', () => {
            const ogCounter = new MultipleCounter({ a: 1, b: 2 });
            ogCounter.merge({ b: 3, c: 4 });
            expect(ogCounter.toJSON()).toEqual({ a: 1, b: 5, c: 4 });
        });
        it('with invalid data', () => {
            const ogCounter = new MultipleCounter();
            expect(() => ogCounter.merge('a' as any)).toThrowError('Invalid data type for merge');
        });
    });
});


suite('QuantileArray', () => {
    const array = new QuantileArray(4, 2);
    test('min data', () => {
        array.count(0);
        expect(array.result()).toEqual({ enoughData: false });
    });
    test('zeros only', () => {
        array.count(0);
        array.count(0);
        array.count(0);
        expect(array.result()).toEqual({
            enoughData: true,
            count: 4,
            p5: 0,
            p25: 0,
            p50: 0,
            p75: 0,
            p95: 0,
        });
    });
    const repeatedExpectedResult = {
        enoughData: true,
        count: 4,
        p5: 0,
        p25: 0,
        p50: 0.5,
        p75: 1,
        p95: 1,
    }
    test('calc quantile', () => {
        array.count(1);
        array.count(1);
        expect(array.result()).toEqual(repeatedExpectedResult);
    });
    test('summary', () => {
        expect(array.resultSummary('ms')).toEqual({
            ...repeatedExpectedResult,
            summary: 'p5:0ms/p25:0ms/p50:1ms/p75:1ms/p95:1ms (x4)',
        });
        expect(array.resultSummary()).toEqual({
            ...repeatedExpectedResult,
            summary: 'p5:0/p25:0/p50:1/p75:1/p95:1 (x4)',
        });
    });
    test('clear', () => {
        array.clear();
        expect(array.result()).toEqual({ enoughData: false });
        expect(array.resultSummary()).toEqual({
            enoughData: false,
            summary: 'not enough data available',
        });
    });
});


suite('TimeCounter', async () => {
    const counter = new TimeCounter();
    await new Promise((resolve) => setTimeout(resolve, 150));
    const duration = counter.stop();

    // Check if the duration is a valid object
    test('duration is valid', () => {
        expect(duration.seconds).toBeTypeOf('number');
        expect(duration.milliseconds).toBeTypeOf('number');
        expect(duration.nanoseconds).toBeTypeOf('number');
        expect(counter.toJSON()).toEqual(duration);
    });

    // Check if the duration is within the expected range
    test('duration within range', () => {
        const isCloseTo50ms = (x: number) => (x > 150 && x < 175);
        expect(duration.seconds * 1000).toSatisfy(isCloseTo50ms);
        expect(duration.milliseconds).toSatisfy(isCloseTo50ms);
        expect(duration.nanoseconds / 1_000_000).toSatisfy(isCloseTo50ms);
    });
});


suite('estimateArrayJsonSize', () => {
    test('obeys minimas', () => {
        const result = estimateArrayJsonSize([], 1);
        expect(result).toEqual({ enoughData: false });

        const result2 = estimateArrayJsonSize([1], 2);
        expect(result2).toEqual({ enoughData: false });
    });

    test('calculates size correctly', () => {
        const array = Array.from({ length: 1000 }, (_, i) => ({ id: i, value: `value${i}` }));
        const realFullSize = JSON.stringify(array).length;
        const realElementSize = realFullSize / array.length;
        const result = estimateArrayJsonSize(array, 100);
        expect(result.enoughData).toBe(true);
        expect(result.bytesTotal).toSatisfy((x: number) => isWithinMargin(x, realFullSize, 0.1));
        expect(result.bytesPerElement).toSatisfy((x: number) => isWithinMargin(x, realElementSize, 0.1));
    });

    test('handles small arrays', () => {
        const array = [{ id: 1, value: 'value1' }];
        const result = estimateArrayJsonSize(array, 0);
        expect(result.enoughData).toBe(true);
        expect(result.bytesTotal).toBeGreaterThan(0);
        expect(result.bytesTotal).toBeLessThan(100);
        expect(result.bytesPerElement).toBeGreaterThan(0);
        expect(result.bytesTotal).toBeLessThan(100);
    });

    test('handles large arrays', () => {
        const array = Array.from({ length: 20000 }, (_, i) => ({ id: i, value: `value${i}` }));
        const realFullSize = JSON.stringify(array).length;
        const realElementSize = realFullSize / array.length;
        const result = estimateArrayJsonSize(array, 100);
        expect(result.enoughData).toBe(true);
        expect(result.bytesTotal).toSatisfy((x: number) => isWithinMargin(x, realFullSize, 0.1));
        expect(result.bytesPerElement).toSatisfy((x: number) => isWithinMargin(x, realElementSize, 0.1));
    });
});
