/**
 * Developer-mode diagnostics for the mesh data plane.
 *
 * The `[Mesh:diag]` logs are gated on the shared `developer_mode` setting, so
 * they must be silent in production (the default) and appear only when an
 * operator turns developer mode on. They must also never carry a node's
 * api_token: the cross-fleet inspect path runs that token in a Bearer header,
 * and a diagnostic or error log that echoed it would leak a long-lived
 * credential into the log surface.
 */
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
import { setupTestDb, cleanupTestDb } from './helpers/setupTestDb';

let tmpDir: string;
let DatabaseService: typeof import('../services/DatabaseService').DatabaseService;
let MeshService: typeof import('../services/MeshService').MeshService;

function captureConsole() {
    const lines: string[] = [];
    const record = (...args: unknown[]): void => {
        lines.push(args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' '));
    };
    const spies = [
        vi.spyOn(console, 'debug').mockImplementation(record),
        vi.spyOn(console, 'log').mockImplementation(record),
        vi.spyOn(console, 'warn').mockImplementation(record),
        vi.spyOn(console, 'error').mockImplementation(record),
    ];
    return { lines, restore: (): void => spies.forEach((s) => s.mockRestore()) };
}

beforeAll(async () => {
    tmpDir = await setupTestDb();
    ({ DatabaseService } = await import('../services/DatabaseService'));
    ({ MeshService } = await import('../services/MeshService'));
});

beforeEach(() => {
    DatabaseService.getInstance().getDb().prepare('DELETE FROM mesh_stacks').run();
    DatabaseService.getInstance().updateGlobalSetting('developer_mode', '0');
});

afterEach(() => {
    vi.restoreAllMocks();
    DatabaseService.getInstance().updateGlobalSetting('developer_mode', '0');
});

afterAll(() => cleanupTestDb(tmpDir));

describe('mesh developer-mode diagnostics', () => {
    it('emits no [Mesh:diag] logs when developer_mode is off', async () => {
        const cap = captureConsole();
        await MeshService.getInstance().refreshAliasCache();
        cap.restore();
        expect(cap.lines.some((l) => l.includes('[Mesh:diag]'))).toBe(false);
    });

    it('emits the [Mesh:diag] refresh log when developer_mode is on', async () => {
        DatabaseService.getInstance().updateGlobalSetting('developer_mode', '1');
        const cap = captureConsole();
        await MeshService.getInstance().refreshAliasCache();
        cap.restore();
        expect(cap.lines.some((l) => l.includes('[Mesh:diag] alias cache refreshed'))).toBe(true);
    });

    it('emits a [Mesh:diag] line from a second instrumented method (enable-for-node)', async () => {
        const db = DatabaseService.getInstance();
        const localNodeId = db.getDefaultNode()?.id ?? 1;
        db.updateGlobalSetting('developer_mode', '1');
        const cap = captureConsole();
        // enableForNode on the local node is a pure DB write plus the diag and
        // activity log; the proxy-dial branch only runs for remote proxy nodes.
        await MeshService.getInstance().enableForNode(localNodeId);
        cap.restore();
        expect(cap.lines.some((l) => l.includes('[Mesh:diag] enable-for-node'))).toBe(true);
    });

    it('never leaks a remote node api token through diagnostics or logs', async () => {
        // A JWT-shaped credential canary, so the assertion guards the actual
        // class of secret (a Bearer/JWT node token) rather than an opaque string.
        const SECRET = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtZXNoLWNhbmFyeSJ9.bWVzaC1jYW5hcnlfc2ln';
        const db = DatabaseService.getInstance();
        const remoteId = db.addNode({
            name: 'obs-remote',
            type: 'remote',
            compose_dir: '',
            is_default: false,
            // Port 1 refuses immediately, so the inspect fetch fails fast without
            // a real peer; the token still travels in the Bearer header.
            api_url: 'http://127.0.0.1:1',
            api_token: SECRET,
        });
        db.insertMeshStack(remoteId, 'obs-stack', 'tester');
        db.updateGlobalSetting('developer_mode', '1');

        const activity: unknown[] = [];
        const unsub = MeshService.getInstance().subscribeActivity((e) => activity.push(e));
        const cap = captureConsole();
        await MeshService.getInstance().refreshAliasCache();
        cap.restore();
        unsub();

        const haystack = `${cap.lines.join('\n')}\n${JSON.stringify(activity)}`;
        expect(haystack).not.toContain(SECRET);
    });
});
