import { describe, it, expect, vi, beforeEach } from 'vitest';
import path from 'path';
import { 
  extractImportStatements,
  extractDomainFromImportPath,
  scanDomainFiles,
  analyzeDomainDependencies,
  detectCircularDependencies,
  generateGraphData,
  type DomainNode
} from '../src/utils/dependency-analyzer';
import type { FileSystem } from '../src/utils/file-operations';

// Mock FileSystem for tests
const createMockFileSystem = (files: Record<string, string> = {}, directories: string[] = []): FileSystem => ({
  writeFile: vi.fn() as any,
  readFile: vi.fn().mockImplementation(async (filePath: string) => {
    if (files[filePath]) {
      return files[filePath];
    }
    throw new Error(`File not found: ${filePath}`);
  }) as any,
  readdir: vi.fn().mockImplementation(async (dirPath: string) => {
    const dirEntries = Object.keys(files)
      .filter(file => path.dirname(file) === dirPath)
      .map(file => path.basename(file));
    
    const subDirs = directories
      .filter(dir => path.dirname(dir) === dirPath && dir !== dirPath)
      .map(dir => path.basename(dir));
    
    return [...new Set([...dirEntries, ...subDirs])];
  }) as any,
  stat: vi.fn().mockImplementation(async (filePath: string) => ({
    isDirectory: () => directories.includes(filePath),
    isFile: () => files[filePath] !== undefined
  })) as any,
  remove: vi.fn() as any,
  mkdir: vi.fn() as any,
  exists: vi.fn().mockImplementation(async (filePath: string) => {
    return files[filePath] !== undefined || directories.includes(filePath);
  }),
  pathExists: vi.fn(),
  ensureDir: vi.fn()
});

describe('Dependency Analyzer', () => {
  describe('extractImportStatements', () => {
    it('should extract import statements', () => {
      const content = `
import React from 'react';
import { useState, useEffect } from 'react';
import type { User } from '../types';
import * as utils from '../utils';
import { ApiService } from '../../api/ApiService';
export { UserService } from '../services/UserService';
export * from './types';
const someCode = "import fake from 'fake'";
      `;

      const imports = extractImportStatements(content);

      expect(imports).toHaveLength(7);
      expect(imports[0]).toEqual({
        statement: `import React from 'react';`,
        line: 2,
        from: 'react',
        type: 'import'
      });
      expect(imports[1]).toEqual({
        statement: `import { useState, useEffect } from 'react';`,
        line: 3,
        from: 'react',
        type: 'import'
      });
      expect(imports[2]).toEqual({
        statement: `import type { User } from '../types';`,
        line: 4,
        from: '../types',
        type: 'import'
      });
      expect(imports[3]).toEqual({
        statement: `import * as utils from '../utils';`,
        line: 5,
        from: '../utils',
        type: 'import'
      });
      expect(imports[4]).toEqual({
        statement: `import { ApiService } from '../../api/ApiService';`,
        line: 6,
        from: '../../api/ApiService',
        type: 'import'
      });
      expect(imports[5]).toEqual({
        statement: `export { UserService } from '../services/UserService';`,
        line: 7,
        from: '../services/UserService',
        type: 'export'
      });
    });

    it('should handle empty content', () => {
      const imports = extractImportStatements('');
      expect(imports).toHaveLength(0);
    });

    it('should handle content without imports', () => {
      const content = `
const x = 1;
function test() {
  return "hello";
}
      `;
      
      const imports = extractImportStatements(content);
      expect(imports).toHaveLength(0);
    });
  });

  describe('extractDomainFromImportPath', () => {
    it('should extract domain from cross-domain import', () => {
      const currentDomainPath = '/project/src/domains/user/services/UserService.ts';
      const importPath = '../../product/entities/Product';
      
      const domain = extractDomainFromImportPath(importPath, currentDomainPath);
      expect(domain).toBe('product');
    });

    it('should return null for same-domain import', () => {
      const currentDomainPath = '/project/src/domains/user/services/UserService.ts';
      const importPath = '../entities/User';
      
      const domain = extractDomainFromImportPath(importPath, currentDomainPath);
      expect(domain).toBeNull();
    });

    it('should return null for external imports', () => {
      const currentDomainPath = '/project/src/domains/user/services/UserService.ts';
      const importPath = 'react';
      
      const domain = extractDomainFromImportPath(importPath, currentDomainPath);
      expect(domain).toBeNull();
    });

    it('should return null for non-domain imports', () => {
      const currentDomainPath = '/project/src/domains/user/services/UserService.ts';
      const importPath = '../../../utils/helpers';
      
      const domain = extractDomainFromImportPath(importPath, currentDomainPath);
      expect(domain).toBeNull();
    });
  });

  describe('scanDomainFiles', () => {
    it('should scan and return TypeScript files in a domain', async () => {
      const files = {
        '/project/src/domains/user/entities/User.ts': 'content',
        '/project/src/domains/user/services/UserService.ts': 'content',
        '/project/src/domains/user/adapters/UserAdapter.tsx': 'content',
        '/project/src/domains/user/README.md': 'content', // Should be ignored
        '/project/src/domains/user/package.json': 'content' // Should be ignored
      };
      
      const directories = [
        '/project/src/domains/user',
        '/project/src/domains/user/entities',
        '/project/src/domains/user/services',
        '/project/src/domains/user/adapters'
      ];

      const fileSystem = createMockFileSystem(files, directories);
      const result = await scanDomainFiles(fileSystem, '/project/src/domains/user');

      expect(result).toHaveLength(3);
      expect(result).toContain('/project/src/domains/user/entities/User.ts');
      expect(result).toContain('/project/src/domains/user/services/UserService.ts');
      expect(result).toContain('/project/src/domains/user/adapters/UserAdapter.tsx');
    });

    it('should handle empty domain directory', async () => {
      const fileSystem = createMockFileSystem({}, ['/project/src/domains/empty']);
      const result = await scanDomainFiles(fileSystem, '/project/src/domains/empty');

      expect(result).toHaveLength(0);
    });

    it('should handle non-existent domain directory', async () => {
      const fileSystem = createMockFileSystem();
      const result = await scanDomainFiles(fileSystem, '/project/src/domains/nonexistent');

      expect(result).toHaveLength(0);
    });
  });

  describe('analyzeDomainDependencies', () => {
    it('should analyze dependencies correctly', async () => {
      const files = {
        '/project/src/domains/user/services/UserService.ts': `
import { Product } from '../../product/entities/Product';
import { ApiService } from '../../shared/api/ApiService';
import { UserEntity } from '../entities/User';
        `,
        '/project/src/domains/user/entities/User.ts': `
import { Order } from '../../order/entities/Order';
        `
      };

      const directories = [
        '/project/src/domains/user',
        '/project/src/domains/user/services',
        '/project/src/domains/user/entities'
      ];

      const fileSystem = createMockFileSystem(files, directories);
      const result = await analyzeDomainDependencies(
        fileSystem,
        '/project/src/domains/user',
        'user'
      );

      expect(result.dependencies).toHaveLength(3);
      expect(result.dependencies).toContain('product');
      expect(result.dependencies).toContain('shared');
      expect(result.dependencies).toContain('order');
      expect(result.files).toHaveLength(2);
    });

    it('should handle domain with no dependencies', async () => {
      const files = {
        '/project/src/domains/isolated/entities/Entity.ts': `
export interface Entity {
  id: string;
}
        `
      };

      const directories = [
        '/project/src/domains/isolated',
        '/project/src/domains/isolated/entities'
      ];

      const fileSystem = createMockFileSystem(files, directories);
      const result = await analyzeDomainDependencies(
        fileSystem,
        '/project/src/domains/isolated',
        'isolated'
      );

      expect(result.dependencies).toHaveLength(0);
      expect(result.files).toHaveLength(1);
    });
  });

  describe('detectCircularDependencies', () => {
    it('should detect simple circular dependency', () => {
      const nodes: DomainNode[] = [
        {
          id: 'user',
          name: 'user',
          type: 'feature',
          fileCount: 3,
          dependencies: ['product'],
          dependents: ['product'],
          files: []
        },
        {
          id: 'product',
          name: 'product',
          type: 'feature',
          fileCount: 2,
          dependencies: ['user'],
          dependents: ['user'],
          files: []
        }
      ];

      const cycles = detectCircularDependencies(nodes);
      expect(cycles).toHaveLength(1);
      expect(cycles[0]).toEqual(['user', 'product']);
    });

    it('should detect complex circular dependency', () => {
      const nodes: DomainNode[] = [
        {
          id: 'user',
          name: 'user',
          type: 'feature',
          fileCount: 3,
          dependencies: ['product'],
          dependents: ['order'],
          files: []
        },
        {
          id: 'product',
          name: 'product',
          type: 'feature',
          fileCount: 2,
          dependencies: ['order'],
          dependents: ['user'],
          files: []
        },
        {
          id: 'order',
          name: 'order',
          type: 'feature',
          fileCount: 4,
          dependencies: ['user'],
          dependents: ['product'],
          files: []
        }
      ];

      const cycles = detectCircularDependencies(nodes);
      expect(cycles).toHaveLength(1);
      expect(cycles[0]).toEqual(['user', 'product', 'order']);
    });

    it('should return empty array when no cycles exist', () => {
      const nodes: DomainNode[] = [
        {
          id: 'user',
          name: 'user',
          type: 'feature',
          fileCount: 3,
          dependencies: ['shared'],
          dependents: [],
          files: []
        },
        {
          id: 'product',
          name: 'product',
          type: 'feature',
          fileCount: 2,
          dependencies: ['shared'],
          dependents: [],
          files: []
        },
        {
          id: 'shared',
          name: 'shared',
          type: 'shared',
          fileCount: 1,
          dependencies: [],
          dependents: ['user', 'product'],
          files: []
        }
      ];

      const cycles = detectCircularDependencies(nodes);
      expect(cycles).toHaveLength(0);
    });
  });

  describe('generateGraphData', () => {
    it('should generate complete graph data', async () => {
      const files = {
        '/project/src/domains/user/entities/User.ts': `
import { Product } from '../../product/entities/Product';
        `,
        '/project/src/domains/product/entities/Product.ts': `
export interface Product {
  id: string;
}
        `,
        '/project/src/domains/shared/utils/helpers.ts': `
export const helper = () => {};
        `
      };

      const directories = [
        '/project/src',
        '/project/src/domains',
        '/project/src/domains/user',
        '/project/src/domains/user/entities',
        '/project/src/domains/product',
        '/project/src/domains/product/entities',
        '/project/src/domains/shared',
        '/project/src/domains/shared/utils'
      ];

      const fileSystem = createMockFileSystem(files, directories);
      const result = await generateGraphData('/project', fileSystem);

      expect(result.nodes).toHaveLength(3);
      expect(result.edges).toHaveLength(1);
      expect(result.stats.totalDomains).toBe(3);
      expect(result.stats.totalDependencies).toBe(1);
      expect(result.stats.circularDependencyCount).toBe(0);

      const userNode = result.nodes.find(n => n.name === 'user');
      expect(userNode).toBeDefined();
      expect(userNode?.dependencies).toContain('product');
      expect(userNode?.fileCount).toBe(1);

      const productNode = result.nodes.find(n => n.name === 'product');
      expect(productNode).toBeDefined();
      expect(productNode?.dependents).toContain('user');

      const edge = result.edges[0];
      expect(edge).toBeDefined();
      if (edge) {
        expect(edge.from).toBe('user');
        expect(edge.to).toBe('product');
        expect(edge.type).toBe('direct-import');
      }
    });

    it('should handle project with no domains', async () => {
      const directories = ['/project/src'];
      const fileSystem = createMockFileSystem({}, directories);
      
      const result = await generateGraphData('/project', fileSystem);

      expect(result.nodes).toHaveLength(0);
      expect(result.edges).toHaveLength(0);
      expect(result.stats.totalDomains).toBe(0);
      expect(result.stats.totalDependencies).toBe(0);
      expect(result.stats.circularDependencyCount).toBe(0);
    });

    it('should detect and count circular dependencies', async () => {
      const files = {
        '/project/src/domains/user/entities/User.ts': `
import { Product } from '../../product/entities/Product';
        `,
        '/project/src/domains/product/entities/Product.ts': `
import { User } from '../../user/entities/User';
        `
      };

      const directories = [
        '/project/src',
        '/project/src/domains',
        '/project/src/domains/user',
        '/project/src/domains/user/entities',
        '/project/src/domains/product',
        '/project/src/domains/product/entities'
      ];

      const fileSystem = createMockFileSystem(files, directories);
      const result = await generateGraphData('/project', fileSystem);

      expect(result.stats.circularDependencyCount).toBe(1);
      expect(result.circularDependencies).toHaveLength(1);
    });
  });
}); 