/**
 * Unit tests for SystemSpecs + HeuristicModelMemoryEstimator.
 *
 * Strategy:
 *  - HeuristicModelMemoryEstimator is pure — directly drive it with model ids.
 *  - NodeSystemSpecsProvider depends on `os.*` so we test:
 *      a) caching (same instance returned twice),
 *      b) shape (all required fields present, sane numbers).
 *    We don't pin platform-specific values since CI hardware varies.
 */

import {
    NodeSystemSpecsProvider,
    HeuristicModelMemoryEstimator,
} from '../src/system/specs';

describe('HeuristicModelMemoryEstimator', () => {
    const est = new HeuristicModelMemoryEstimator();

    test('extracts parameter count from "7B" suffix', () => {
        // 7B q4 default: 7 * 0.6 + 1 = 5.2
        expect(est.estimate('llama-3.2-7b-q4_K_M')).toBeCloseTo(5.2, 1);
    });

    test('extracts parameter count from "70B"', () => {
        // 70B q4 default: 70 * 0.6 + 1 = 43
        expect(est.estimate('llama-3-70b-instruct-q4_0')).toBeCloseTo(43, 0);
    });

    test('q8 quantization uses higher byte/param', () => {
        // 7B q8: 7 * 1.0 + 1 = 8
        expect(est.estimate('mistral-7b-q8_0')).toBeCloseTo(8, 1);
    });

    test('fp16 uses 2 bytes/param', () => {
        // 7B fp16: 7 * 2.0 + 1 = 15
        expect(est.estimate('mistral-7b-fp16')).toBeCloseTo(15, 1);
    });

    test('q5 sits between q4 and q6', () => {
        const q4 = est.estimate('foo-7b-q4');
        const q5 = est.estimate('foo-7b-q5');
        const q6 = est.estimate('foo-7b-q6');
        expect(q4).toBeLessThan(q5);
        expect(q5).toBeLessThan(q6);
    });

    test('falls back to 7B when parameter count is absent', () => {
        // unknown size → 7B q4 default → 5.2
        expect(est.estimate('some-model-no-size')).toBeCloseTo(5.2, 1);
    });

    test('decimal parameter counts like "3.8b"', () => {
        // 3.8B q4: 3.8 * 0.6 + 1 = 3.28
        expect(est.estimate('phi-3.8b-q4')).toBeCloseTo(3.28, 1);
    });

    test('handles empty / undefined input gracefully', () => {
        expect(est.estimate('')).toBeCloseTo(5.2, 1); // defaults
        expect(est.estimate(undefined as any)).toBeCloseTo(5.2, 1);
    });
});

describe('NodeSystemSpecsProvider', () => {
    test('returns the same cached object on repeated calls', () => {
        const provider = new NodeSystemSpecsProvider();
        const a = provider.get();
        const b = provider.get();
        expect(a).toBe(b);
    });

    test('produces a sane shape', () => {
        const specs = new NodeSystemSpecsProvider().get();
        expect(specs.totalRamGB).toBeGreaterThan(0);
        expect(specs.cpuCount).toBeGreaterThanOrEqual(1);
        expect(specs.platform).toMatch(/^(darwin|linux|win32|freebsd|openbsd|sunos|aix)$/);
        expect(specs.arch.length).toBeGreaterThan(0);
        expect(typeof specs.isAppleSilicon).toBe('boolean');
        expect(specs.safeModelBudgetGB).toBeGreaterThanOrEqual(2);
        expect(specs.safeModelBudgetGB).toBeLessThanOrEqual(specs.totalRamGB);
        expect(specs.summary).toMatch(/RAM/);
    });

    test('safe budget is at most ~65% of total RAM (Apple Silicon ceiling)', () => {
        const specs = new NodeSystemSpecsProvider().get();
        // Even on Apple Silicon (most generous ratio) the budget is capped at
        // 0.65 of total. Use 0.7 as a soft upper bound for any platform.
        expect(specs.safeModelBudgetGB).toBeLessThanOrEqual(specs.totalRamGB * 0.7 + 1);
    });
});
