import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { promises as fs } from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { atomicWriteJsonFile, ensureDir, escapePowerShellLiteral, resolveSkillSourceDirFromExtractRoot, stringifyStableJson, toSdkAppsFromEnabledTools, resolveSkillSourceDir, } from './file-utils.js';
// Mock axios for download tests
vi.mock('axios', () => ({
    default: {
        get: vi.fn(),
        post: vi.fn(),
    },
}));
// Mock config
vi.mock('../config.js', () => ({
    schedulerBaseUrl: 'http://localhost:8080',
}));
vi.mock('../services/runtime-binding.js', () => ({
    getRuntimeAccessToken: vi.fn(() => 'test-token'),
}));
describe('file-utils', () => {
    describe('ensureDir', () => {
        const tempDir = path.join(os.tmpdir(), `aws-file-utils-test-${Date.now()}`);
        afterEach(async () => {
            try {
                await fs.rm(tempDir, { recursive: true, force: true });
            }
            catch {
                // ignore
            }
        });
        it('should create directory if not exists', async () => {
            const result = await ensureDir(tempDir);
            expect(result).toBe(tempDir);
            const stat = await fs.stat(tempDir);
            expect(stat.isDirectory()).toBe(true);
        });
        it('should create nested directories', async () => {
            const nestedDir = path.join(tempDir, 'level1', 'level2', 'level3');
            const result = await ensureDir(nestedDir);
            expect(result).toBe(nestedDir);
            const stat = await fs.stat(nestedDir);
            expect(stat.isDirectory()).toBe(true);
        });
        it('should return path if directory already exists', async () => {
            await fs.mkdir(tempDir, { recursive: true });
            const result = await ensureDir(tempDir);
            expect(result).toBe(tempDir);
        });
    });
    describe('atomicWriteJsonFile', () => {
        const tempDir = path.join(os.tmpdir(), `aws-file-utils-json-test-${Date.now()}`);
        afterEach(async () => {
            await fs.rm(tempDir, { recursive: true, force: true }).catch(() => undefined);
        });
        it('should write stable sorted json for deterministic config files', async () => {
            const filePath = path.join(tempDir, 'config.json');
            await atomicWriteJsonFile(filePath, {
                z: 1,
                nested: { b: true, a: false },
                array: [{ d: 4, c: 3 }],
            });
            expect(await fs.readFile(filePath, 'utf-8')).toBe('{\n' +
                '  "array": [\n' +
                '    {\n' +
                '      "c": 3,\n' +
                '      "d": 4\n' +
                '    }\n' +
                '  ],\n' +
                '  "nested": {\n' +
                '    "a": false,\n' +
                '    "b": true\n' +
                '  },\n' +
                '  "z": 1\n' +
                '}\n');
        });
    });
    describe('stringifyStableJson', () => {
        it('should preserve array order while sorting object keys', () => {
            expect(stringifyStableJson([{ b: 2, a: 1 }, { d: 4, c: 3 }])).toBe('[\n' +
                '  {\n' +
                '    "a": 1,\n' +
                '    "b": 2\n' +
                '  },\n' +
                '  {\n' +
                '    "c": 3,\n' +
                '    "d": 4\n' +
                '  }\n' +
                ']\n');
        });
    });
    describe('escapePowerShellLiteral', () => {
        it('should escape single quotes by doubling them', () => {
            expect(escapePowerShellLiteral("test'value")).toBe("test''value");
        });
        it('should handle multiple single quotes', () => {
            expect(escapePowerShellLiteral("a'b'c")).toBe("a''b''c");
        });
        it('should return empty string for empty input', () => {
            expect(escapePowerShellLiteral('')).toBe('');
        });
        it('should handle null/undefined gracefully', () => {
            expect(escapePowerShellLiteral(null)).toBe('');
            expect(escapePowerShellLiteral(undefined)).toBe('');
        });
        it('should not modify strings without quotes', () => {
            expect(escapePowerShellLiteral('normal path')).toBe('normal path');
        });
        it('should handle path with special characters', () => {
            expect(escapePowerShellLiteral("C:\\Users\\Test's Folder")).toBe("C:\\Users\\Test''s Folder");
        });
    });
    describe('resolveSkillSourceDirFromExtractRoot', () => {
        const tempRoot = path.join(os.tmpdir(), `aws-skill-extract-test-${Date.now()}`);
        beforeEach(async () => {
            await fs.mkdir(tempRoot, { recursive: true });
        });
        afterEach(async () => {
            try {
                await fs.rm(tempRoot, { recursive: true, force: true });
            }
            catch {
                // ignore
            }
        });
        it('should return subdirectory when single visible directory exists', async () => {
            const subDir = path.join(tempRoot, 'skill-package');
            await fs.mkdir(subDir, { recursive: true });
            await fs.writeFile(path.join(subDir, 'skill.md'), 'test', 'utf-8');
            const result = await resolveSkillSourceDirFromExtractRoot(tempRoot);
            expect(result).toBe(subDir);
        });
        it('should return root when multiple visible directories exist', async () => {
            const subDir1 = path.join(tempRoot, 'dir1');
            const subDir2 = path.join(tempRoot, 'dir2');
            await fs.mkdir(subDir1, { recursive: true });
            await fs.mkdir(subDir2, { recursive: true });
            const result = await resolveSkillSourceDirFromExtractRoot(tempRoot);
            expect(result).toBe(tempRoot);
        });
        it('should return root when files exist at root level', async () => {
            await fs.writeFile(path.join(tempRoot, 'skill.md'), 'test', 'utf-8');
            await fs.writeFile(path.join(tempRoot, 'README.md'), 'readme', 'utf-8');
            const result = await resolveSkillSourceDirFromExtractRoot(tempRoot);
            expect(result).toBe(tempRoot);
        });
        it('should ignore hidden directories (starting with dot)', async () => {
            const visibleDir = path.join(tempRoot, 'visible');
            const hiddenDir = path.join(tempRoot, '.hidden');
            await fs.mkdir(visibleDir, { recursive: true });
            await fs.mkdir(hiddenDir, { recursive: true });
            const result = await resolveSkillSourceDirFromExtractRoot(tempRoot);
            expect(result).toBe(visibleDir);
        });
        it('should return root when no visible entries exist', async () => {
            const hiddenDir = path.join(tempRoot, '.hidden');
            await fs.mkdir(hiddenDir, { recursive: true });
            const result = await resolveSkillSourceDirFromExtractRoot(tempRoot);
            expect(result).toBe(tempRoot);
        });
    });
    describe('toSdkAppsFromEnabledTools', () => {
        it('should map enabled tools to app flags', () => {
            const result = toSdkAppsFromEnabledTools(['claude', 'opencode']);
            expect(result.claude).toBe(true);
            expect(result.opencode).toBe(true);
            expect(result.codex).toBe(false);
            expect(result.gemini).toBe(false);
        });
        it('should handle all known tools', () => {
            const result = toSdkAppsFromEnabledTools([
                'claude',
                'claudecode',
                'codex',
                'gemini',
                'opencode',
                'openclaw',
            ]);
            expect(result.claude).toBe(true);
            expect(result.codex).toBe(true);
            expect(result.gemini).toBe(true);
            expect(result.opencode).toBe(true);
            expect(result.openclaw).toBe(true);
        });
        it('should return all false for empty array', () => {
            const result = toSdkAppsFromEnabledTools([]);
            expect(result.claude).toBe(false);
            expect(result.codex).toBe(false);
            expect(result.gemini).toBe(false);
            expect(result.opencode).toBe(false);
            expect(result.openclaw).toBe(false);
        });
        it('should handle null/undefined input', () => {
            const resultNull = toSdkAppsFromEnabledTools(null);
            const resultUndefined = toSdkAppsFromEnabledTools(undefined);
            expect(resultNull.claude).toBe(false);
            expect(resultUndefined.claude).toBe(false);
        });
        it('should ignore unknown tools', () => {
            const result = toSdkAppsFromEnabledTools(['unknown-tool', 'claude']);
            expect(result.claude).toBe(true);
        });
    });
    describe('resolveSkillSourceDir', () => {
        it('should return sourceDir when available', () => {
            const skill = { sourceDir: '/path/to/source' };
            expect(resolveSkillSourceDir(skill)).toBe(path.resolve('/path/to/source'));
        });
        it('should fallback to directory when sourceDir not available', () => {
            const skill = { directory: '/path/to/directory' };
            expect(resolveSkillSourceDir(skill)).toBe(path.resolve('/path/to/directory'));
        });
        it('should fallback to filePath when sourceDir and directory not available', () => {
            const skill = { filePath: '/path/to/file.yaml' };
            expect(resolveSkillSourceDir(skill)).toBe(path.resolve('/path/to/file.yaml'));
        });
        it('should prioritize sourceDir over directory and filePath', () => {
            const skill = {
                sourceDir: '/source',
                directory: '/directory',
                filePath: '/file',
            };
            expect(resolveSkillSourceDir(skill)).toBe(path.resolve('/source'));
        });
        it('should throw error when no path is available', () => {
            const skill = {};
            expect(() => resolveSkillSourceDir(skill)).toThrow('skill.sourceDir is required');
        });
        it('should throw error when skill is null', () => {
            expect(() => resolveSkillSourceDir(null)).toThrow('skill.sourceDir is required');
        });
        it('should throw error when path is empty string', () => {
            const skill = { sourceDir: '' };
            expect(() => resolveSkillSourceDir(skill)).toThrow('skill.sourceDir is required');
        });
        it('should throw error when path is whitespace only', () => {
            const skill = { sourceDir: '   ' };
            expect(() => resolveSkillSourceDir(skill)).toThrow('skill.sourceDir is required');
        });
        it('should trim whitespace from path', () => {
            const skill = { sourceDir: '  /path/to/source  ' };
            expect(resolveSkillSourceDir(skill)).toBe(path.resolve('/path/to/source'));
        });
    });
});
