import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { mkdtempSync, rmSync, writeFileSync, readFileSync, existsSync, readdirSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { configureSettingsPath, configureSettingsLogger, loadSettingsFromDisk, getDefaultLanguage, setDefaultLanguage, getModel, setModel, getSettings, __resetForTestsOnly, } from '../../../../frameworks/settings/runtimeSettings.js';
describe('runtimeSettings', () => {
    describe('in-memory API (no path configured)', () => {
        beforeEach(() => {
            __resetForTestsOnly();
        });
        it('defaults language to "en"', () => {
            expect(getDefaultLanguage()).toBe('en');
        });
        it('changes language to "fr"', async () => {
            await setDefaultLanguage('fr');
            expect(getDefaultLanguage()).toBe('fr');
        });
        it('includes language in getSettings()', async () => {
            await setDefaultLanguage('fr');
            expect(getSettings().language).toBe('fr');
        });
    });
    describe('persistence', () => {
        let directory;
        let settingsPath;
        beforeEach(() => {
            directory = mkdtempSync(join(tmpdir(), 'reviewflow-settings-'));
            settingsPath = join(directory, 'settings.json');
            __resetForTestsOnly();
            configureSettingsPath(settingsPath);
        });
        afterEach(() => {
            __resetForTestsOnly();
            rmSync(directory, { recursive: true, force: true });
        });
        describe('loadSettingsFromDisk', () => {
            it('restores language and model from existing file', async () => {
                writeFileSync(settingsPath, JSON.stringify({ language: 'fr', model: 'sonnet' }));
                await loadSettingsFromDisk();
                expect(getDefaultLanguage()).toBe('fr');
                expect(getModel()).toBe('sonnet');
            });
            it('creates the file with defaults when it does not exist', async () => {
                await loadSettingsFromDisk();
                expect(existsSync(settingsPath)).toBe(true);
                const written = JSON.parse(readFileSync(settingsPath, 'utf-8'));
                expect(written).toEqual({ language: 'en', model: 'opus', worktreeStaleThresholdHours: 24 });
            });
            it('falls back to defaults silently when file is malformed JSON', async () => {
                writeFileSync(settingsPath, '{ this is not valid json');
                await loadSettingsFromDisk();
                expect(getDefaultLanguage()).toBe('en');
                expect(getModel()).toBe('opus');
            });
            it('falls back to defaults silently when language is unknown', async () => {
                writeFileSync(settingsPath, JSON.stringify({ language: 'es', model: 'opus' }));
                await loadSettingsFromDisk();
                expect(getDefaultLanguage()).toBe('en');
            });
            it('falls back to defaults silently when model is unknown', async () => {
                writeFileSync(settingsPath, JSON.stringify({ language: 'fr', model: 'gpt-5' }));
                await loadSettingsFromDisk();
                expect(getModel()).toBe('opus');
            });
        });
        describe('persistence on set', () => {
            it('persists language to disk after setDefaultLanguage', async () => {
                await loadSettingsFromDisk();
                await setDefaultLanguage('fr');
                const written = JSON.parse(readFileSync(settingsPath, 'utf-8'));
                expect(written.language).toBe('fr');
            });
            it('persists model to disk after setModel', async () => {
                await loadSettingsFromDisk();
                await setModel('sonnet');
                const written = JSON.parse(readFileSync(settingsPath, 'utf-8'));
                expect(written.model).toBe('sonnet');
            });
            it('survives a simulated process restart', async () => {
                await loadSettingsFromDisk();
                await setDefaultLanguage('fr');
                await setModel('sonnet');
                __resetForTestsOnly();
                configureSettingsPath(settingsPath);
                await loadSettingsFromDisk();
                expect(getDefaultLanguage()).toBe('fr');
                expect(getModel()).toBe('sonnet');
            });
            it('writes atomically (no .tmp leftover after persist)', async () => {
                await loadSettingsFromDisk();
                await setDefaultLanguage('fr');
                const entries = readdirSync(directory);
                expect(entries.filter((name) => name.endsWith('.tmp'))).toEqual([]);
            });
            it('preserves both fields across concurrent setModel and setDefaultLanguage', async () => {
                await loadSettingsFromDisk();
                await Promise.all([setModel('sonnet'), setDefaultLanguage('fr')]);
                const written = JSON.parse(readFileSync(settingsPath, 'utf-8'));
                expect(written).toEqual({ language: 'fr', model: 'sonnet', worktreeStaleThresholdHours: 24 });
            });
        });
        describe('validation', () => {
            it('throws when setModel receives an unknown model', async () => {
                await expect(setModel('gpt-5')).rejects.toThrow(/Invalid model/);
            });
        });
        describe('worktreeStaleThresholdHours', () => {
            it('defaults to 24 when no settings file exists', async () => {
                await loadSettingsFromDisk();
                const { getWorktreeStaleThresholdHours } = await import('../../../../frameworks/settings/runtimeSettings.js');
                expect(getWorktreeStaleThresholdHours()).toBe(24);
            });
            it('restores worktreeStaleThresholdHours from an existing file', async () => {
                writeFileSync(settingsPath, JSON.stringify({ language: 'en', model: 'opus', worktreeStaleThresholdHours: 48 }));
                await loadSettingsFromDisk();
                const { getWorktreeStaleThresholdHours } = await import('../../../../frameworks/settings/runtimeSettings.js');
                expect(getWorktreeStaleThresholdHours()).toBe(48);
            });
            it('falls back to the default when the persisted value is below the minimum', async () => {
                writeFileSync(settingsPath, JSON.stringify({ language: 'en', model: 'opus', worktreeStaleThresholdHours: 0 }));
                await loadSettingsFromDisk();
                const { getWorktreeStaleThresholdHours } = await import('../../../../frameworks/settings/runtimeSettings.js');
                expect(getWorktreeStaleThresholdHours()).toBe(24);
            });
            it('persists worktreeStaleThresholdHours after setWorktreeStaleThresholdHours', async () => {
                await loadSettingsFromDisk();
                const { setWorktreeStaleThresholdHours } = await import('../../../../frameworks/settings/runtimeSettings.js');
                await setWorktreeStaleThresholdHours(72);
                const written = JSON.parse(readFileSync(settingsPath, 'utf-8'));
                expect(written.worktreeStaleThresholdHours).toBe(72);
            });
            it('throws when setWorktreeStaleThresholdHours receives a value outside [1, 720]', async () => {
                await loadSettingsFromDisk();
                const { setWorktreeStaleThresholdHours } = await import('../../../../frameworks/settings/runtimeSettings.js');
                await expect(setWorktreeStaleThresholdHours(0)).rejects.toThrow(/Invalid stale threshold/);
                await expect(setWorktreeStaleThresholdHours(721)).rejects.toThrow(/Invalid stale threshold/);
            });
        });
        describe('logger injection', () => {
            it('reports malformed JSON via the injected logger instead of console', async () => {
                const warnings = [];
                configureSettingsLogger({ warn: (message) => warnings.push(message) });
                writeFileSync(settingsPath, '{ not valid json');
                await loadSettingsFromDisk();
                expect(warnings).toHaveLength(1);
                expect(warnings[0]).toMatch(/malformed json/i);
            });
            it('reports schema violation via the injected logger', async () => {
                const warnings = [];
                configureSettingsLogger({ warn: (message) => warnings.push(message) });
                writeFileSync(settingsPath, JSON.stringify({ language: 'es', model: 'opus' }));
                await loadSettingsFromDisk();
                expect(warnings).toHaveLength(1);
                expect(warnings[0]).toMatch(/invalid settings/i);
            });
        });
    });
});
//# sourceMappingURL=runtimeSettings.test.js.map