import * as fs from 'node:fs/promises';
import * as os from 'node:os';
import * as nodePath from 'node:path';
import type { Mock } from 'vitest';

vi.mock('node:os', async () => {
	const actual = await vi.importActual<typeof os>('node:os');
	return { ...actual, homedir: vi.fn(() => actual.homedir()) };
});

import type { GatewayConfig } from './config';
import { SettingsStore } from './settings-store';
import { isAllDeny } from './startup-config-cli';

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function parseJson<T>(raw: string): T {
	try {
		return JSON.parse(raw) as T;
	} catch {
		throw new Error(`Failed to parse JSON: ${raw}`);
	}
}

const BASE_CONFIG: GatewayConfig = {
	logLevel: 'info',
	allowedOrigins: [],
	filesystem: { dir: '/tmp' },
	computer: { shell: { timeout: 30_000 } },
	browser: {
		defaultBrowser: 'chrome',
	},
	permissions: {},
	permissionConfirmation: 'instance',
};

// ---------------------------------------------------------------------------
// isAllDeny
// ---------------------------------------------------------------------------

describe('isAllDeny', () => {
	it('returns true when all groups are deny', () => {
		expect(
			isAllDeny({
				filesystemRead: 'deny',
				filesystemWrite: 'deny',
				shell: 'deny',
				computer: 'deny',
				browser: 'deny',
			}),
		).toBe(true);
	});

	it('returns false when at least one group is ask', () => {
		expect(
			isAllDeny({
				filesystemRead: 'ask',
				filesystemWrite: 'deny',
				shell: 'deny',
				computer: 'deny',
				browser: 'deny',
			}),
		).toBe(false);
	});

	it('returns false when at least one group is allow', () => {
		expect(
			isAllDeny({
				filesystemRead: 'allow',
				filesystemWrite: 'deny',
				shell: 'deny',
				computer: 'deny',
				browser: 'deny',
			}),
		).toBe(false);
	});
});

// ---------------------------------------------------------------------------
// SettingsStore.ensureInitialized (CLI entry path; replaces legacy ensureSettingsFile helper)
// ---------------------------------------------------------------------------

describe('SettingsStore.ensureInitialized', () => {
	let tmpDir: string;

	beforeEach(async () => {
		tmpDir = await fs.mkdtemp(nodePath.join(os.tmpdir(), 'gateway-test-'));
		// Point getSettingsFilePath() at our temp location by overriding homedir
		(os.homedir as Mock).mockReturnValue(tmpDir);
	});

	afterEach(async () => {
		vi.restoreAllMocks();
		await fs.rm(tmpDir, { recursive: true, force: true });
	});

	it('creates the settings file with recommended defaults when absent', async () => {
		await SettingsStore.ensureInitialized(BASE_CONFIG);

		const raw = await fs.readFile(nodePath.join(tmpDir, '.n8n-gateway', 'settings.json'), 'utf-8');
		const parsed = parseJson<Record<string, unknown>>(raw);

		expect(parsed.permissions).toMatchObject({
			filesystemRead: 'allow',
			filesystemWrite: 'ask',
			shell: 'deny',
			computer: 'deny',
			browser: 'ask',
		});
		expect(parsed.filesystemDir).toBe('');
		expect(parsed.resourcePermissions).toEqual({});
	});

	it('merges CLI/ENV permission overrides on top of the template', async () => {
		const config: GatewayConfig = {
			...BASE_CONFIG,
			permissions: { shell: 'allow' },
		};
		await SettingsStore.ensureInitialized(config);

		const raw = await fs.readFile(nodePath.join(tmpDir, '.n8n-gateway', 'settings.json'), 'utf-8');
		const parsed = parseJson<{ permissions: Record<string, string> }>(raw);

		// CLI override wins
		expect(parsed.permissions.shell).toBe('allow');
		// Template defaults for the rest
		expect(parsed.permissions.filesystemRead).toBe('allow');
	});

	it('does not overwrite an existing settings file', async () => {
		const dir = nodePath.join(tmpDir, '.n8n-gateway');
		const file = nodePath.join(dir, 'settings.json');
		await fs.mkdir(dir, { recursive: true });
		const existing = JSON.stringify({ permissions: { shell: 'allow' }, filesystemDir: '/custom' });
		await fs.writeFile(file, existing, 'utf-8');

		await SettingsStore.ensureInitialized(BASE_CONFIG);

		const raw = await fs.readFile(file, 'utf-8');
		expect(raw).toBe(existing);
	});

	it('is safe to call multiple times — only creates once', async () => {
		await SettingsStore.ensureInitialized(BASE_CONFIG);
		await SettingsStore.ensureInitialized(BASE_CONFIG);

		// Second call must not throw and must not alter the file
		const raw = await fs.readFile(nodePath.join(tmpDir, '.n8n-gateway', 'settings.json'), 'utf-8');
		const parsed = parseJson<Record<string, unknown>>(raw);
		expect(parsed.filesystemDir).toBe('');
	});
});
