'use strict';

const test = require('node:test');
const assert = require('node:assert');

const HealthStatus = require('../../src/domain/HealthStatus');

test('ok() returns the canonical zero-level shape', () => {
    const h = HealthStatus.ok();
    assert.strictEqual(h.level, 0);
    assert.deepStrictEqual(h.flags, []);
    assert.strictEqual(h.message, 'nominal');
    assert.strictEqual(h.source, null);
    assert.ok(Object.isFrozen(h));
    assert.ok(Object.isFrozen(h.flags));
});

test('ok(message, source) carries through optional args', () => {
    const h = HealthStatus.ok('all good', 'aggregator');
    assert.strictEqual(h.level, 0);
    assert.strictEqual(h.message, 'all good');
    assert.strictEqual(h.source, 'aggregator');
});

test('degraded(2, [...], msg, src) returns the right frozen shape', () => {
    const h = HealthStatus.degraded(2, ['x'], 'msg', 'src');
    assert.strictEqual(h.level, 2);
    assert.deepStrictEqual(h.flags, ['x']);
    assert.strictEqual(h.message, 'msg');
    assert.strictEqual(h.source, 'src');
    assert.ok(Object.isFrozen(h));
    assert.ok(Object.isFrozen(h.flags));
    // Mutation attempts must not change the frozen flags array.
    assert.throws(() => { h.flags.push('y'); }, TypeError);
});

test('degraded clamps out-of-range levels (high)', () => {
    const h = HealthStatus.degraded(7, ['hot'], 'too high');
    assert.strictEqual(h.level, 3);
});

test('degraded clamps out-of-range levels (low / non-numeric)', () => {
    const lo = HealthStatus.degraded(0, ['lo'], 'too low');
    assert.strictEqual(lo.level, 1);
    const nan = HealthStatus.degraded('nope', ['n'], 'bad input');
    assert.strictEqual(nan.level, 1);
});

test('degraded falls back to label-derived message when message is empty', () => {
    const h = HealthStatus.degraded(2, ['x']);
    assert.strictEqual(h.message, 'major');
});

test('compose([]) returns ok()', () => {
    const h = HealthStatus.compose([]);
    assert.strictEqual(h.level, 0);
    assert.deepStrictEqual(h.flags, []);
    assert.strictEqual(h.message, 'nominal');
    assert.strictEqual(h.source, null);
});

test('compose merges, picking worst level + that status\'s message/source', () => {
    const h = HealthStatus.compose([
        HealthStatus.ok(),
        HealthStatus.degraded(1, ['a'], 'a-msg', 'a-src'),
        HealthStatus.degraded(2, ['b'], 'b-msg', 'b-src'),
    ]);
    assert.strictEqual(h.level, 2);
    assert.deepStrictEqual(h.flags, ['a', 'b']);
    assert.strictEqual(h.message, 'b-msg');
    assert.strictEqual(h.source, 'b-src');
});

test('compose ties: first worst-level status wins for message/source', () => {
    const h = HealthStatus.compose([
        HealthStatus.degraded(2, ['a'], 'first', 'first-src'),
        HealthStatus.degraded(2, ['b'], 'second', 'second-src'),
    ]);
    assert.strictEqual(h.level, 2);
    assert.strictEqual(h.message, 'first');
    assert.strictEqual(h.source, 'first-src');
});

test('compose dedupes flags across statuses', () => {
    const h = HealthStatus.compose([
        HealthStatus.degraded(1, ['x', 'y'], 'one'),
        HealthStatus.degraded(2, ['y', 'z', 'x'], 'two'),
    ]);
    assert.deepStrictEqual(h.flags, ['x', 'y', 'z']);
});

test('label maps 0..3 → nominal/minor/major/critical', () => {
    assert.strictEqual(HealthStatus.label(0), 'nominal');
    assert.strictEqual(HealthStatus.label(1), 'minor');
    assert.strictEqual(HealthStatus.label(2), 'major');
    assert.strictEqual(HealthStatus.label(3), 'critical');
});

test('label returns "unknown" for out-of-range levels', () => {
    assert.strictEqual(HealthStatus.label(-1), 'unknown');
    assert.strictEqual(HealthStatus.label(4), 'unknown');
    assert.strictEqual(HealthStatus.label('x'), 'unknown');
});
