import * as fs from 'fs';

import {
  filterValidPaths,
  findConflictingPath,
  getFolderName,
  isDuplicatePath,
  isValidDirectoryPath,
  normalizePathForComparison,
  validateDirectoryPath,
} from '@/utils/externalContext';

jest.mock('fs');

describe('externalContext utilities', () => {
  describe('normalizePathForComparison', () => {
    const originalPlatform = process.platform;
    const expectNormalized = (input: string, expected: string) => {
      const normalized = normalizePathForComparison(input);
      const resolved = process.platform === 'win32' ? expected.toLowerCase() : expected;
      expect(normalized).toBe(resolved);
    };

    afterEach(() => {
      Object.defineProperty(process, 'platform', { value: originalPlatform });
    });

    // eslint-disable-next-line jest/expect-expect
    it('should convert backslashes to forward slashes', () => {
      expectNormalized('C:\\Users\\test', 'C:/Users/test');
      expectNormalized('path\\to\\file', 'path/to/file');
    });

    // eslint-disable-next-line jest/expect-expect
    it('should remove trailing slashes', () => {
      expectNormalized('/path/to/dir/', '/path/to/dir');
      expectNormalized('/path/to/dir///', '/path/to/dir');
    });

    // eslint-disable-next-line jest/expect-expect
    it('should handle combined cases', () => {
      expectNormalized('C:\\Users\\test\\', 'C:/Users/test');
      expectNormalized('C:\\Users\\test\\subdir\\', 'C:/Users/test/subdir');
    });

    // eslint-disable-next-line jest/expect-expect
    it('should handle paths without trailing slashes', () => {
      expectNormalized('/path/to/dir', '/path/to/dir');
      expectNormalized('C:/Users/test', 'C:/Users/test');
    });

    // eslint-disable-next-line jest/expect-expect
    it('should handle Unix-style paths', () => {
      expectNormalized('/home/user/project', '/home/user/project');
      expectNormalized('/home/user/project/', '/home/user/project');
    });

    it('should normalize Windows case when platform is win32', () => {
      Object.defineProperty(process, 'platform', { value: 'win32' });
      expect(normalizePathForComparison('C:\\Users\\Test\\Docs')).toBe('c:/users/test/docs');
    });

    it('should translate MSYS paths on Windows', () => {
      Object.defineProperty(process, 'platform', { value: 'win32' });
      expect(normalizePathForComparison('/c/Users/Test')).toBe('c:/users/test');
    });
  });

  describe('findConflictingPath', () => {
    describe('child path detection (adding child when parent exists)', () => {
      it('should detect when new path is inside existing path', () => {
        const result = findConflictingPath('/parent/child', ['/parent']);
        expect(result).toEqual({ path: '/parent', type: 'parent' });
      });

      it('should detect deeply nested child paths', () => {
        const result = findConflictingPath('/parent/child/grandchild', ['/parent']);
        expect(result).toEqual({ path: '/parent', type: 'parent' });
      });

      it('should detect with multiple existing paths', () => {
        const result = findConflictingPath('/workspace/project', ['/other', '/workspace', '/another']);
        expect(result).toEqual({ path: '/workspace', type: 'parent' });
      });

      it('should detect with Windows paths', () => {
        const result = findConflictingPath('C:\\Users\\test\\project', ['C:\\Users\\test']);
        expect(result).toEqual({ path: 'C:\\Users\\test', type: 'parent' });
      });
    });

    describe('parent path detection (adding parent when child exists)', () => {
      it('should detect when new path would contain existing path', () => {
        const result = findConflictingPath('/parent', ['/parent/child']);
        expect(result).toEqual({ path: '/parent/child', type: 'child' });
      });

      it('should detect when new path would contain deeply nested path', () => {
        const result = findConflictingPath('/parent', ['/parent/child/grandchild']);
        expect(result).toEqual({ path: '/parent/child/grandchild', type: 'child' });
      });

      it('should detect with Windows paths', () => {
        const result = findConflictingPath('C:\\Users\\test', ['C:\\Users\\test\\project']);
        expect(result).toEqual({ path: 'C:\\Users\\test\\project', type: 'child' });
      });
    });

    describe('unrelated paths (should be allowed)', () => {
      it('should return null for completely unrelated paths', () => {
        const result = findConflictingPath('/project1', ['/project2']);
        expect(result).toBeNull();
      });

      it('should return null for sibling paths', () => {
        const result = findConflictingPath('/parent/sibling1', ['/parent/sibling2']);
        expect(result).toBeNull();
      });

      it('should return null when no existing paths', () => {
        const result = findConflictingPath('/new/path', []);
        expect(result).toBeNull();
      });

      it('should return null for paths with similar prefixes but not nested', () => {
        // /vault vs /vault2 - should NOT be considered nested
        const result = findConflictingPath('/vault2', ['/vault']);
        expect(result).toBeNull();
      });

      it('should handle Windows paths with similar prefixes', () => {
        const result = findConflictingPath('C:\\Users\\test2', ['C:\\Users\\test']);
        expect(result).toBeNull();
      });
    });

    describe('edge cases', () => {
      it('should handle trailing slashes correctly', () => {
        const result = findConflictingPath('/parent/child/', ['/parent/']);
        expect(result).toEqual({ path: '/parent/', type: 'parent' });
      });

      it('should handle mixed path separators', () => {
        const result = findConflictingPath('/parent\\child', ['/parent']);
        expect(result).toEqual({ path: '/parent', type: 'parent' });
      });

      it('should handle exact same path', () => {
        // Exact same paths are not nested (handled by duplicate check elsewhere)
        const result = findConflictingPath('/parent', ['/parent']);
        expect(result).toBeNull();
      });

      it('should return first conflict when multiple exist', () => {
        const result = findConflictingPath('/a/b', ['/a', '/a/b/c']);
        // Should return /a as it appears first and is a parent
        expect(result).toEqual({ path: '/a', type: 'parent' });
      });
    });
  });

  describe('getFolderName', () => {
    it('should extract folder name from Unix path', () => {
      expect(getFolderName('/Users/test/workspace')).toBe('workspace');
    });

    it('should extract folder name from Windows path', () => {
      expect(getFolderName('C:\\Users\\test\\workspace')).toBe('workspace');
    });

    it('should handle trailing slashes', () => {
      expect(getFolderName('/Users/test/workspace/')).toBe('workspace');
    });

    it('should handle single segment paths', () => {
      expect(getFolderName('workspace')).toBe('workspace');
    });

    it('should handle root paths', () => {
      expect(getFolderName('/')).toBe('');
    });
  });

  describe('validateDirectoryPath', () => {
    beforeEach(() => {
      jest.clearAllMocks();
    });

    it('should return valid for existing directory', () => {
      (fs.statSync as jest.Mock).mockReturnValue({ isDirectory: () => true });
      expect(validateDirectoryPath('/existing/dir')).toEqual({ valid: true });
    });

    it('should return not-a-directory error for file path', () => {
      (fs.statSync as jest.Mock).mockReturnValue({ isDirectory: () => false });
      expect(validateDirectoryPath('/path/to/file.txt')).toEqual({
        valid: false,
        error: 'Path exists but is not a directory',
      });
    });

    it('should return ENOENT error for non-existent path', () => {
      const err = new Error('ENOENT') as NodeJS.ErrnoException;
      err.code = 'ENOENT';
      (fs.statSync as jest.Mock).mockImplementation(() => { throw err; });
      expect(validateDirectoryPath('/non/existent')).toEqual({
        valid: false,
        error: 'Path does not exist',
      });
    });

    it('should return permission denied error for EACCES', () => {
      const err = new Error('EACCES') as NodeJS.ErrnoException;
      err.code = 'EACCES';
      (fs.statSync as jest.Mock).mockImplementation(() => { throw err; });
      expect(validateDirectoryPath('/restricted/path')).toEqual({
        valid: false,
        error: 'Permission denied',
      });
    });

    it('should return generic error for unknown errors', () => {
      const err = new Error('Something went wrong') as NodeJS.ErrnoException;
      err.code = 'EIO';
      (fs.statSync as jest.Mock).mockImplementation(() => { throw err; });
      const result = validateDirectoryPath('/some/path');
      expect(result.valid).toBe(false);
      expect(result.error).toContain('Cannot access path');
      expect(result.error).toContain('Something went wrong');
    });
  });

  describe('isValidDirectoryPath', () => {
    beforeEach(() => {
      jest.clearAllMocks();
    });

    it('should return true for existing directory', () => {
      (fs.statSync as jest.Mock).mockReturnValue({ isDirectory: () => true });
      expect(isValidDirectoryPath('/existing/dir')).toBe(true);
      expect(fs.statSync).toHaveBeenCalledWith('/existing/dir');
    });

    it('should return false for non-existent path', () => {
      (fs.statSync as jest.Mock).mockImplementation(() => {
        throw new Error('ENOENT');
      });
      expect(isValidDirectoryPath('/non/existent')).toBe(false);
    });

    it('should return false for file path (not directory)', () => {
      (fs.statSync as jest.Mock).mockReturnValue({ isDirectory: () => false });
      expect(isValidDirectoryPath('/path/to/file.txt')).toBe(false);
    });
  });

  describe('filterValidPaths', () => {
    beforeEach(() => {
      jest.clearAllMocks();
    });

    it('should filter out non-existent paths', () => {
      (fs.statSync as jest.Mock).mockImplementation((p: string) => {
        if (p === '/valid/path') {
          return { isDirectory: () => true };
        }
        throw new Error('ENOENT');
      });

      const result = filterValidPaths(['/valid/path', '/invalid/path', '/another/invalid']);
      expect(result).toEqual(['/valid/path']);
    });

    it('should return empty array when all paths are invalid', () => {
      (fs.statSync as jest.Mock).mockImplementation(() => {
        throw new Error('ENOENT');
      });

      const result = filterValidPaths(['/invalid1', '/invalid2']);
      expect(result).toEqual([]);
    });

    it('should return all paths when all are valid', () => {
      (fs.statSync as jest.Mock).mockReturnValue({ isDirectory: () => true });

      const paths = ['/path1', '/path2', '/path3'];
      const result = filterValidPaths(paths);
      expect(result).toEqual(paths);
    });

    it('should handle empty array', () => {
      const result = filterValidPaths([]);
      expect(result).toEqual([]);
    });
  });

  describe('isDuplicatePath', () => {
    const originalPlatform = process.platform;

    afterEach(() => {
      Object.defineProperty(process, 'platform', { value: originalPlatform });
    });

    it('should detect exact duplicate paths', () => {
      expect(isDuplicatePath('/path/to/dir', ['/path/to/dir'])).toBe(true);
    });

    it('should return false for non-duplicate paths', () => {
      expect(isDuplicatePath('/path/to/dir', ['/other/path'])).toBe(false);
    });

    it('should detect duplicates with different trailing slashes', () => {
      expect(isDuplicatePath('/path/to/dir/', ['/path/to/dir'])).toBe(true);
      expect(isDuplicatePath('/path/to/dir', ['/path/to/dir/'])).toBe(true);
    });

    it('should detect duplicates with mixed path separators', () => {
      expect(isDuplicatePath('/path\\to\\dir', ['/path/to/dir'])).toBe(true);
    });

    it('should detect case-insensitive duplicates on Windows', () => {
      Object.defineProperty(process, 'platform', { value: 'win32' });
      expect(isDuplicatePath('C:\\Users\\Test', ['c:\\users\\test'])).toBe(true);
      expect(isDuplicatePath('C:/Users/Test', ['c:/users/test'])).toBe(true);
    });

    it('should handle empty existing paths array', () => {
      expect(isDuplicatePath('/path/to/dir', [])).toBe(false);
    });

    it('should check against multiple existing paths', () => {
      expect(isDuplicatePath('/path/b', ['/path/a', '/path/b', '/path/c'])).toBe(true);
      expect(isDuplicatePath('/path/d', ['/path/a', '/path/b', '/path/c'])).toBe(false);
    });
  });
});
