import { test, expect, describe, beforeAll, afterAll } from 'bun:test';
import path from 'path';
import fs from 'fs/promises';
import os from 'os';
import { listFiles } from '../../src/server/processor/actor/list_files';

describe('listFiles', () => {
    const testDir = path.join(os.tmpdir(), 'list-files-tests');
    const subDir = path.join(testDir, 'subdir');

    beforeAll(async () => {
        // Create test directory structure
        await fs.mkdir(testDir, { recursive: true });
        await fs.mkdir(subDir, { recursive: true });

        // Create various test files
        await fs.writeFile(path.join(testDir, 'file1.txt'), 'content1');
        await fs.writeFile(path.join(testDir, 'file2.txt'), 'content2');
        await fs.writeFile(path.join(testDir, 'file3.md'), 'content3');
        await fs.writeFile(path.join(testDir, 'file4.js'), 'content4');
        await fs.writeFile(path.join(subDir, 'nested1.txt'), 'nested1');
        await fs.writeFile(path.join(subDir, 'nested2.md'), 'nested2');
    });

    afterAll(async () => {
        // Clean up test files
        await fs.rm(testDir, { recursive: true, force: true });
    });

    test('should list all files in directory when no pattern specified', async () => {
        const result = await listFiles({ path: testDir });

        expect(result.query).toContain('Found');
        expect(result.query).toContain('files');
        expect(result.compiled).toContain('file1.txt');
        expect(result.compiled).toContain('file2.txt');
        expect(result.compiled).toContain('file3.md');
        expect(result.compiled).toContain('file4.js');
        expect(result.file).toBe(testDir);
        expect(result.uri).toBe(testDir);
    });

    test('should filter files by pattern', async () => {
        const result = await listFiles({
            path: testDir,
            pattern: '*.txt',
        });

        expect(result.compiled).toContain('file1.txt');
        expect(result.compiled).toContain('file2.txt');
        expect(result.compiled).not.toContain('file3.md');
        expect(result.compiled).not.toContain('file4.js');
        expect(result.query).toContain('matching "*.txt"');
    });

    test('should handle glob patterns with multiple extensions', async () => {
        const result = await listFiles({
            path: testDir,
            pattern: '*.{txt,md}',
        });

        expect(result.compiled).toContain('file1.txt');
        expect(result.compiled).toContain('file2.txt');
        expect(result.compiled).toContain('file3.md');
        expect(result.compiled).not.toContain('file4.js');
    });

    test('should list files recursively with ** pattern', async () => {
        const result = await listFiles({
            path: testDir,
            pattern: '**/*.txt',
        });

        expect(result.compiled).toContain('file1.txt');
        expect(result.compiled).toContain('file2.txt');
        expect(result.compiled).toContain('nested1.txt');
        expect(result.compiled).not.toContain('file3.md');
    });

    test('should return no files message when no matches found', async () => {
        const result = await listFiles({
            path: testDir,
            pattern: '*.xyz',
        });

        expect(result.compiled).toBe('No files found.');
        expect(result.query).toContain('Found 0 files');
    });

    test('should return file list sorted by modification time (newest first)', async () => {
        const result = await listFiles({ path: testDir });

        // The implementation sorts files by modification time (newest first), not alphabetically
        // Just verify that files are returned in a consistent format
        const files = result.compiled.split('\n').map(line => line.replace('- ', ''));
        expect(files.length).toBeGreaterThan(0);
        expect(files.every(f => f.includes(testDir))).toBe(true);
    });

    test('should format file list with bullet points', async () => {
        const result = await listFiles({ path: testDir });

        const lines = result.compiled.split('\n');
        expect(lines.every(line => line.startsWith('- '))).toBe(true);
    });

    test('should resolve relative paths from current directory', async () => {
        // Create a test directory in tmp (sandbox-safe)
        const relativeTestDir = path.join(os.tmpdir(), 'list-files-test-relative');
        await fs.mkdir(relativeTestDir, { recursive: true });
        await fs.writeFile(path.join(relativeTestDir, 'relative-test.txt'), 'test');

        const result = await listFiles({ path: relativeTestDir });

        expect(result.compiled).toContain('relative-test.txt');

        // Clean up
        await fs.rm(relativeTestDir, { recursive: true, force: true });
    });

    test('should handle absolute paths correctly', async () => {
        const result = await listFiles({ path: testDir });

        expect(result.compiled).toContain('file1.txt');
    });

    // Perf guard: home dir listing should complete in <3s.
    // Catches regressions in directory listing speed.
    test('should list home directory files quickly', async () => {
        const start = performance.now();
        const result = await listFiles({});
        const elapsed = performance.now() - start;

        expect(result.file).toBe('');
        expect(result.compiled).not.toBe('No files found.');
        expect(elapsed, "list files tool has become slow").toBeLessThan(3000);
    });

    test('should handle non-existent directory gracefully', async () => {
        const nonExistentDir = path.join(testDir, 'does-not-exist');
        const result = await listFiles({ path: nonExistentDir });

        expect(result.compiled).toBe('Path does not exist.');
    });

    test('should preserve on-disk casing in returned paths when input case differs', async () => {
        const notesDirActual = path.join(testDir, 'Notes');
        await fs.mkdir(notesDirActual, { recursive: true });
        const fileActual = path.join(notesDirActual, 'tasks.md');
        await fs.writeFile(fileActual, 'tasks');

        const notesDirWrongCase = path.join(testDir, 'notes');

        let caseInsensitive = false;
        try {
            await fs.stat(notesDirWrongCase);
            caseInsensitive = true;
        } catch {
            caseInsensitive = false;
        }

        const result = await listFiles({ path: notesDirWrongCase });

        if (!caseInsensitive) {
            expect(result.compiled).toBe('Path does not exist.');
            return;
        }

        expect(result.compiled).toContain(fileActual);
        expect(result.compiled).not.toContain(path.join(testDir, 'notes', 'tasks.md'));
    });

    test('should include file count in query', async () => {
        const result = await listFiles({ path: testDir });

        const lines = result.compiled.split('\n');
        const fileCount = lines.length;

        expect(result.query).toContain(`Found ${fileCount} files`);
    });

    test('should include path in query when specified', async () => {
        const result = await listFiles({ path: testDir });

        expect(result.query).toContain(`in ${testDir}`);
    });

    test('should include pattern in query when specified', async () => {
        const result = await listFiles({
            path: testDir,
            pattern: '*.txt',
        });

        expect(result.query).toContain('matching "*.txt"');
    });

    test('should handle pattern matching specific filenames', async () => {
        const result = await listFiles({
            path: testDir,
            pattern: 'file1.txt',
        });

        expect(result.compiled).toContain('file1.txt');
        expect(result.compiled).not.toContain('file2.txt');
    });

    test('should handle complex glob patterns', async () => {
        const result = await listFiles({
            path: testDir,
            pattern: 'file[1-2].*',
        });

        expect(result.compiled).toContain('file1.txt');
        expect(result.compiled).toContain('file2.txt');
        expect(result.compiled).not.toContain('file3.md');
        expect(result.compiled).not.toContain('file4.js');
    });

    test('should return absolute paths in file list', async () => {
        const result = await listFiles({ path: testDir });

        const files = result.compiled.split('\n');
        expect(files.every(line => {
            const filePath = line.replace('- ', '');
            return path.isAbsolute(filePath);
        })).toBe(true);
    });

    test('should handle directories with special characters', async () => {
        const specialDir = path.join(testDir, 'special-dir!@#');
        await fs.mkdir(specialDir, { recursive: true });
        await fs.writeFile(path.join(specialDir, 'special.txt'), 'content');

        const result = await listFiles({ path: specialDir });

        expect(result.compiled).toContain('special.txt');

        // Clean up
        await fs.rm(specialDir, { recursive: true, force: true });
    });

    test('should handle empty directory', async () => {
        const emptyDir = path.join(testDir, 'empty-dir');
        await fs.mkdir(emptyDir, { recursive: true });

        const result = await listFiles({ path: emptyDir });

        expect(result.compiled).toBe('No files found.');

        // Clean up
        await fs.rmdir(emptyDir);
    });

    test('should list files and directories', async () => {
        const result = await listFiles({ path: testDir });

        // Should include files
        expect(result.compiled).toContain('file1.txt');
        // glob includes directories as well
        expect(result.compiled).toContain('subdir');
    });
});
