import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as fs from 'fs-extra';
import path from 'path';
import { executeVisualize, type VisualizeOptions } from '../../src/commands/visualize';
import { generateGraphData } from '../../src/utils/dependency-analyzer';
import { createFileSystem, type FileSystem } from '../../src/utils/file-operations';
import { logger } from '../../src/utils/logger';

// Mock des dépendances
vi.mock('../../src/utils/dependency-analyzer');
vi.mock('../../src/utils/file-operations');
vi.mock('../../src/utils/logger', () => ({
  logger: {
    error: vi.fn(),
    success: vi.fn(),
    info: vi.fn(),
    warning: vi.fn(),
    log: vi.fn(),
  }
}));
vi.mock('fs-extra');
vi.mock('fs');
vi.mock('express');
vi.mock('child_process');

const mockGenerateGraphData = vi.mocked(generateGraphData);
const mockCreateFileSystem = vi.mocked(createFileSystem);
const mockLogger = vi.mocked(logger);
const mockFs = vi.mocked(fs);

// Mock fs
const mockWriteFile = vi.hoisted(() => vi.fn().mockResolvedValue(undefined));

const mockFsNode = vi.hoisted(() => ({
  default: {
    promises: {
      writeFile: mockWriteFile
    }
  },
  promises: {
    writeFile: mockWriteFile
  }
}));

vi.mock('fs', () => mockFsNode);

// Mock console methods
const consoleSpy = {
  log: vi.spyOn(console, 'log').mockImplementation(() => {}),
  error: vi.spyOn(console, 'error').mockImplementation(() => {})
};

// Mock data pour les tests
const mockGraphData = {
  nodes: [
    {
      id: 'user',
      name: 'user',
      type: 'feature',
      fileCount: 5,
      dependencies: ['shared'],
      dependents: [],
      files: ['user/entities/User.ts', 'user/services/UserService.ts']
    },
    {
      id: 'shared',
      name: 'shared',
      type: 'shared',
      fileCount: 3,
      dependencies: [],
      dependents: ['user'],
      files: ['shared/utils/helpers.ts']
    }
  ],
  edges: [
    {
      from: 'user',
      to: 'shared',
      type: 'direct-import' as const,
      count: 1,
      metadata: {
        files: ['user/services/UserService.ts']
      }
    }
  ],
  circularDependencies: [],
  stats: {
    totalDomains: 2,
    totalDependencies: 1,
    circularDependencyCount: 0
  }
};

const mockGraphDataWithCircularDeps = {
  ...mockGraphData,
  circularDependencies: [['user', 'product']],
  stats: {
    ...mockGraphData.stats,
    circularDependencyCount: 1
  }
};

describe('Visualize Command', () => {
  let mockFileSystem: FileSystem;

  beforeEach(() => {
    // Reset tous les mocks
    vi.clearAllMocks();
    
    // Reset mock fs
    mockWriteFile.mockClear();
    mockWriteFile.mockResolvedValue(undefined);
    
    // Setup mock FileSystem
    mockFileSystem = {
      writeFile: vi.fn() as any,
      readFile: vi.fn() as any,
      readdir: vi.fn() as any,
      stat: vi.fn() as any,
      remove: vi.fn() as any,
      mkdir: vi.fn() as any,
      exists: vi.fn() as any,
      pathExists: vi.fn() as any,
      ensureDir: vi.fn() as any
    };

    mockCreateFileSystem.mockReturnValue(mockFileSystem);
    mockGenerateGraphData.mockResolvedValue(mockGraphData);
    
    // Mock des méthodes du logger
    mockLogger.info = vi.fn();
    mockLogger.success = vi.fn();
    mockLogger.warning = vi.fn();
    mockLogger.error = vi.fn();
  });

  afterEach(() => {
    consoleSpy.log.mockClear();
    consoleSpy.error.mockClear();
  });

  describe('executeVisualize - format console', () => {
    it('should display console visualization', async () => {
      const options: VisualizeOptions = {
        format: 'console',
        projectRoot: '/test-project'
      };

      await executeVisualize(options);

      expect(mockGenerateGraphData).toHaveBeenCalledWith('/test-project', mockFileSystem);
      expect(mockLogger.info).toHaveBeenCalledWith('🔍 Analyzing domain dependencies...');
      expect(mockLogger.success).toHaveBeenCalledWith('Found 2 domains with 1 dependencies');
      
      // Vérifier que la sortie console a été appelée
      expect(consoleSpy.log).toHaveBeenCalledWith('\n🏗️  RUCH Domain Visualization\n');
      expect(consoleSpy.log).toHaveBeenCalledWith(expect.stringContaining('Domains: 2'));
      expect(consoleSpy.log).toHaveBeenCalledWith(expect.stringContaining('Dependencies: 1'));
    });

    it('should warn about circular dependencies in console', async () => {
      mockGenerateGraphData.mockResolvedValue(mockGraphDataWithCircularDeps);
      
      const options: VisualizeOptions = {
        format: 'console',
        projectRoot: '/test-project'
      };

      await executeVisualize(options);

      expect(mockLogger.warning).toHaveBeenCalledWith('⚠️  Found 1 circular dependencies');
      // La sortie console est générée par la fonction displayConsoleVisualization
      expect(consoleSpy.log).toHaveBeenCalled();
    });

    it('should handle empty project gracefully', async () => {
      const emptyGraphData = {
        nodes: [],
        edges: [],
        circularDependencies: [],
        stats: { totalDomains: 0, totalDependencies: 0, circularDependencyCount: 0 }
      };
      
      mockGenerateGraphData.mockResolvedValue(emptyGraphData);

      const options: VisualizeOptions = {
        format: 'console',
        projectRoot: '/empty-project'
      };

      await executeVisualize(options);

      expect(mockLogger.warning).toHaveBeenCalledWith('No domains found in src/domains/. Make sure you have domains created.');
    });
  });

  describe('executeVisualize - format json', () => {
    it('should save graph data to JSON file', async () => {
      const options: VisualizeOptions = {
        format: 'json',
        output: '/test-project/custom-output.json',
        projectRoot: '/test-project'
      };

      await executeVisualize(options);

      expect(mockGenerateGraphData).toHaveBeenCalledWith('/test-project', mockFileSystem);
      expect(mockWriteFile).toHaveBeenCalledWith(
        '/test-project/custom-output.json',
        JSON.stringify(mockGraphData, null, 2)
      );
      expect(mockLogger.success).toHaveBeenCalledWith('Graph data saved to: /test-project/custom-output.json');
    });

    it('should use default output path if not specified', async () => {
      const options: VisualizeOptions = {
        format: 'json',
        projectRoot: '/test-project'
      };

      await executeVisualize(options);

      expect(mockWriteFile).toHaveBeenCalledWith(
        '/test-project/graph-data.json',
        JSON.stringify(mockGraphData, null, 2)
      );
      expect(mockLogger.success).toHaveBeenCalledWith('Graph data saved to: /test-project/graph-data.json');
    });
  });

  describe('executeVisualize - format app', () => {
    it('should handle app format initialization', async () => {
      const options: VisualizeOptions = {
        format: 'app',
        port: 3001,
        projectRoot: '/test-project',
        open: false
      };

      // Mock spawn pour le build process
      const { spawn } = await import('child_process');
      const mockSpawn = vi.mocked(spawn);
      
      const mockBuildProcess = {
        stdout: { on: vi.fn() },
        stderr: { on: vi.fn() },
        on: vi.fn((event, callback) => {
          if (event === 'close') {
            callback(0); // Success exit code
          }
        })
      };
      
      mockSpawn.mockReturnValue(mockBuildProcess as any);

      // Mock express
      const mockExpress = await import('express');
      const mockApp = {
        get: vi.fn(),
        use: vi.fn(),
        listen: vi.fn((port, callback) => {
          callback();
          return {
            on: vi.fn(),
            close: vi.fn()
          };
        })
      };
      
      vi.mocked(mockExpress.default).mockReturnValue(mockApp as any);

      await executeVisualize(options);

      expect(mockLogger.info).toHaveBeenCalledWith('📦 Building React app...');
      expect(mockLogger.success).toHaveBeenCalledWith('✅ React app built successfully');
      expect(mockLogger.info).toHaveBeenCalledWith('🚀 Starting visualization server...');
    });
  });

  describe('executeVisualize - error handling', () => {
    it('should handle analysis errors gracefully', async () => {
      const error = new Error('Analysis failed');
      mockGenerateGraphData.mockRejectedValue(error);

      const options: VisualizeOptions = {
        format: 'console',
        projectRoot: '/test-project'
      };

      await expect(executeVisualize(options)).rejects.toThrow('Analysis failed');
    });

    it('should handle file system errors', async () => {
      const options: VisualizeOptions = {
        format: 'json',
        output: '/invalid/path/output.json',
        projectRoot: '/test-project'
      };

      // Mock une erreur de fs pour ce test
      mockWriteFile.mockRejectedValueOnce(new Error('Permission denied'));

      await expect(executeVisualize(options)).rejects.toThrow('Permission denied');
    });
  });

  describe('executeVisualize - options validation', () => {
    it('should use default options when not provided', async () => {
      const options: VisualizeOptions = {};

      await executeVisualize(options);

      expect(mockGenerateGraphData).toHaveBeenCalledWith(process.cwd(), mockFileSystem);
    });

    it('should handle custom project root', async () => {
      const options: VisualizeOptions = {
        projectRoot: '/custom/project/path',
        format: 'console'
      };

      await executeVisualize(options);

      expect(mockGenerateGraphData).toHaveBeenCalledWith('/custom/project/path', mockFileSystem);
    });

    it('should parse port correctly', async () => {
      const options: VisualizeOptions = {
        port: 4000,
        format: 'console'
      };

      await executeVisualize(options);

      // Port should be parsed but not used in console format
      expect(mockGenerateGraphData).toHaveBeenCalled();
    });
  });

  describe('Display functions', () => {
    it('should display domains information correctly', async () => {
      const options: VisualizeOptions = {
        format: 'console',
        projectRoot: '/test-project'
      };

      await executeVisualize(options);

      // Vérifier que la sortie console a été appelée
      expect(consoleSpy.log).toHaveBeenCalled();
    });

    it('should display statistics correctly', async () => {
      const options: VisualizeOptions = {
        format: 'console',
        projectRoot: '/test-project'
      };

      await executeVisualize(options);

      expect(consoleSpy.log).toHaveBeenCalled();
    });

    it('should show tips at the end', async () => {
      const options: VisualizeOptions = {
        format: 'console',
        projectRoot: '/test-project'
      };

      await executeVisualize(options);

      expect(consoleSpy.log).toHaveBeenCalled();
    });
  });
}); 