import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';

import { type ExternalContextFile,externalContextScanner } from '@/utils/externalContextScanner';

describe('externalContextScanner', () => {
  let tempDir: string;

  beforeEach(() => {
    // Create a temp directory for testing
    tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'claudian-test-'));

    // Create test file structure
    fs.mkdirSync(path.join(tempDir, 'subdir'));
    fs.writeFileSync(path.join(tempDir, 'file1.txt'), 'content1');
    fs.writeFileSync(path.join(tempDir, 'file2.md'), 'content2');
    fs.writeFileSync(path.join(tempDir, 'subdir', 'file3.ts'), 'content3');

    // Create hidden file (should be skipped)
    fs.writeFileSync(path.join(tempDir, '.hidden'), 'hidden');

    // Clear cache before each test
    externalContextScanner.invalidateCache();
  });

  afterEach(() => {
    // Clean up temp directory
    fs.rmSync(tempDir, { recursive: true, force: true });
  });

  describe('scanPaths', () => {
    it('should scan directory and return files', () => {
      const files = externalContextScanner.scanPaths([tempDir]);

      expect(files.length).toBe(3);
      expect(files.map((f: ExternalContextFile) => f.name).sort()).toEqual(['file1.txt', 'file2.md', 'file3.ts']);
    });

    it('should include file metadata', () => {
      const files = externalContextScanner.scanPaths([tempDir]);
      const file1 = files.find((f: ExternalContextFile) => f.name === 'file1.txt');

      expect(file1).toBeDefined();
      expect(file1!.path).toBe(path.join(tempDir, 'file1.txt'));
      expect(file1!.relativePath).toBe('file1.txt');
      expect(file1!.contextRoot).toBe(tempDir);
      expect(file1!.mtime).toBeGreaterThan(0);
    });

    it('should include files in subdirectories', () => {
      const files = externalContextScanner.scanPaths([tempDir]);
      const file3 = files.find((f: ExternalContextFile) => f.name === 'file3.ts');

      expect(file3).toBeDefined();
      expect(file3!.relativePath).toBe(path.join('subdir', 'file3.ts'));
    });

    it('should skip hidden files', () => {
      const files = externalContextScanner.scanPaths([tempDir]);
      const hidden = files.find((f: ExternalContextFile) => f.name === '.hidden');

      expect(hidden).toBeUndefined();
    });

    it('should skip hidden directories', () => {
      // Create a hidden directory with a file
      const hiddenDir = path.join(tempDir, '.hidden-dir');
      fs.mkdirSync(hiddenDir);
      fs.writeFileSync(path.join(hiddenDir, 'secret.txt'), 'secret');

      const files = externalContextScanner.scanPaths([tempDir]);
      const secret = files.find((f: ExternalContextFile) => f.name === 'secret.txt');

      expect(secret).toBeUndefined();
    });

    it('should skip node_modules', () => {
      // Create node_modules with a file
      const nodeModules = path.join(tempDir, 'node_modules');
      fs.mkdirSync(nodeModules);
      fs.writeFileSync(path.join(nodeModules, 'package.json'), '{}');

      const files = externalContextScanner.scanPaths([tempDir]);
      const pkg = files.find((f: ExternalContextFile) => f.name === 'package.json');

      expect(pkg).toBeUndefined();
    });

    it('should handle non-existent paths', () => {
      const files = externalContextScanner.scanPaths(['/non/existent/path']);

      expect(files).toEqual([]);
    });

    it('should handle multiple external context paths', () => {
      // Create a second temp directory
      const tempDir2 = fs.mkdtempSync(path.join(os.tmpdir(), 'claudian-test2-'));
      fs.writeFileSync(path.join(tempDir2, 'file4.js'), 'content4');

      try {
        const files = externalContextScanner.scanPaths([tempDir, tempDir2]);

        expect(files.length).toBe(4);
        expect(files.map((f: ExternalContextFile) => f.name).sort()).toEqual([
          'file1.txt',
          'file2.md',
          'file3.ts',
          'file4.js',
        ]);
      } finally {
        fs.rmSync(tempDir2, { recursive: true, force: true });
      }
    });
  });

  describe('caching', () => {
    it('should cache results', () => {
      // First scan
      const files1 = externalContextScanner.scanPaths([tempDir]);

      // Add a new file
      fs.writeFileSync(path.join(tempDir, 'new-file.txt'), 'new content');

      // Second scan should use cache
      const files2 = externalContextScanner.scanPaths([tempDir]);

      expect(files1.length).toBe(files2.length);
      expect(files2.find((f: ExternalContextFile) => f.name === 'new-file.txt')).toBeUndefined();
    });

    it('should respect cache invalidation', () => {
      // First scan
      externalContextScanner.scanPaths([tempDir]);

      // Add a new file
      fs.writeFileSync(path.join(tempDir, 'new-file.txt'), 'new content');

      // Invalidate cache
      externalContextScanner.invalidateCache();

      // Second scan should see new file
      const files = externalContextScanner.scanPaths([tempDir]);

      expect(files.find((f: ExternalContextFile) => f.name === 'new-file.txt')).toBeDefined();
    });

    it('should invalidate specific path', () => {
      // Create a second temp directory
      const tempDir2 = fs.mkdtempSync(path.join(os.tmpdir(), 'claudian-test2-'));
      fs.writeFileSync(path.join(tempDir2, 'file4.js'), 'content4');

      try {
        // First scan both paths
        externalContextScanner.scanPaths([tempDir, tempDir2]);

        // Add files to both
        fs.writeFileSync(path.join(tempDir, 'new1.txt'), 'new1');
        fs.writeFileSync(path.join(tempDir2, 'new2.txt'), 'new2');

        // Invalidate only first path
        externalContextScanner.invalidatePath(tempDir);

        // Second scan
        const files = externalContextScanner.scanPaths([tempDir, tempDir2]);

        // Should see new file in first path (invalidated)
        expect(files.find((f: ExternalContextFile) => f.name === 'new1.txt')).toBeDefined();
        // Should NOT see new file in second path (still cached)
        expect(files.find((f: ExternalContextFile) => f.name === 'new2.txt')).toBeUndefined();
      } finally {
        fs.rmSync(tempDir2, { recursive: true, force: true });
      }
    });
  });

  describe('home path expansion', () => {
    it('should expand home paths', () => {
      // This test uses a real path that should exist
      const files = externalContextScanner.scanPaths(['~']);

      // Should not throw and should return some files (or empty if home is empty)
      expect(Array.isArray(files)).toBe(true);
    });
  });
});
