import { describe, it, expect, beforeEach, vi } from 'vitest';
import fs from 'fs-extra';
import path from 'path';
import { logger } from '../../src/utils/logger';
import { 
  initMsw, 
  generateMswHandlers, 
  generateMswMocks, 
  updateMsw,
  updateMswDomain 
} from '../../src/commands/msw';
import * as templates from '../../src/templates/msw';
import * as dynamicTemplates from '../../src/templates/msw-dynamic';
import * as domainAnalyzer from '../../src/utils/domain-analyzer';
import * as domainFileAnalyzer from '../../src/utils/domain-file-analyzer';

// Mock all dependencies
vi.mock('fs-extra');
vi.mock('../../src/utils/logger', () => ({
  logger: {
    error: vi.fn(),
    success: vi.fn(),
    info: vi.fn(),
    warning: vi.fn(),
    log: vi.fn(),
  }
}));
vi.mock('../../src/templates/msw', () => ({
  getMswSetupTemplate: vi.fn(),
  getMswBrowserTemplate: vi.fn(),
  getMswServerTemplate: vi.fn(),
  getMswHandlersTemplate: vi.fn(),
  getMswHandlersIndexTemplate: vi.fn(),
  getMswMocksTemplate: vi.fn(),
  getMswSetupTestsTemplate: vi.fn(),
}));
vi.mock('../../src/templates/msw-dynamic', () => ({
  generateDynamicMswHandlers: vi.fn(),
  generateDynamicMockData: vi.fn(),
}));
vi.mock('../../src/utils/domain-analyzer', () => ({
  getExistingDomains: vi.fn(),
}));
vi.mock('../../src/utils/domain-file-analyzer', () => ({
  analyzeDomain: vi.fn(),
}));

describe('MSW Commands', () => {
  const projectRoot = '/test/project';
  const srcPath = path.join(projectRoot, 'src');
  const mswPath = path.join(srcPath, 'mocks');
  const handlersPath = path.join(mswPath, 'handlers');

  beforeEach(() => {
    vi.clearAllMocks();
  });

  describe('initMsw', () => {
    it('should initialize MSW configuration when not exists', async () => {
      const { getMswSetupTemplate, getMswBrowserTemplate, getMswServerTemplate, getMswSetupTestsTemplate, getMswHandlersIndexTemplate } = await import('../../src/templates/msw');
      
      (fs.exists as any).mockResolvedValue(false);
      (fs.ensureDir as any).mockResolvedValue(undefined);
      (fs.writeFile as any).mockResolvedValue(undefined);
      (getMswSetupTemplate as any).mockReturnValue('// setup');
      (getMswBrowserTemplate as any).mockReturnValue('// browser');
      (getMswServerTemplate as any).mockReturnValue('// server');
      (getMswSetupTestsTemplate as any).mockReturnValue('// setupTests');
      (getMswHandlersIndexTemplate as any).mockReturnValue('// handlers index');

      await initMsw(fs, logger, projectRoot);

      expect(fs.exists).toHaveBeenCalledWith(path.join(mswPath, 'setup.ts'));
      expect(fs.ensureDir).toHaveBeenCalledWith(mswPath);
      expect(fs.ensureDir).toHaveBeenCalledWith(handlersPath);
      expect(fs.writeFile).toHaveBeenCalledTimes(5);
      expect(logger.success).toHaveBeenCalledWith('MSW initialized successfully!');
      expect(logger.info).toHaveBeenCalledWith('💡 Use "ruch msw handlers generate" to create domain handlers');
    });

    it('should not initialize when already exists', async () => {
      (fs.exists as any).mockResolvedValue(true);

      await initMsw(fs, logger, projectRoot);

      expect(fs.ensureDir).not.toHaveBeenCalled();
      expect(fs.writeFile).not.toHaveBeenCalled();
      expect(logger.warning).toHaveBeenCalledWith('MSW is already initialized in this project');
    });

    it('should handle errors during initialization', async () => {
      (fs.exists as any).mockResolvedValue(false);
      (fs.ensureDir as any).mockRejectedValue(new Error('Permission denied'));

      await initMsw(fs, logger, projectRoot);

      expect(logger.error).toHaveBeenCalledWith('Error initializing MSW: Permission denied');
    });
  });

  describe('generateMswHandlers', () => {
    it('should generate handlers for all domains', async () => {
      const { getExistingDomains } = await import('../../src/utils/domain-analyzer');
      const { getMswHandlersTemplate, getMswSetupTemplate, getMswBrowserTemplate, getMswServerTemplate, getMswHandlersIndexTemplate } = await import('../../src/templates/msw');
      
      const mockDomains = [
        { name: 'user', path: '/test/domains/user' },
        { name: 'product', path: '/test/domains/product' }
      ];

      (fs.exists as any).mockImplementation((filePath: string) => {
        if (filePath === mswPath) return Promise.resolve(true);
        if (filePath.includes('user.ts') || filePath.includes('product.ts')) return Promise.resolve(false);
        return Promise.resolve(false);
      });
      (fs.ensureDir as any).mockResolvedValue(undefined);
      (fs.writeFile as any).mockResolvedValue(undefined);
      (getExistingDomains as any).mockResolvedValue(mockDomains);
      (getMswHandlersTemplate as any).mockReturnValue('// handler');
      (getMswSetupTemplate as any).mockReturnValue('// setup');
      (getMswBrowserTemplate as any).mockReturnValue('// browser');
      (getMswServerTemplate as any).mockReturnValue('// server');
      (getMswHandlersIndexTemplate as any).mockReturnValue('// handlers index');

      await generateMswHandlers(fs, logger, projectRoot);

      expect(getExistingDomains).toHaveBeenCalledWith(fs, projectRoot);
      expect(fs.writeFile).toHaveBeenCalledWith(
        path.join(handlersPath, 'user.ts'),
        '// handler',
        'utf8'
      );
      expect(fs.writeFile).toHaveBeenCalledWith(
        path.join(handlersPath, 'product.ts'),
        '// handler',
        'utf8'
      );
      expect(logger.success).toHaveBeenCalledWith('MSW handlers generated successfully!');
      expect(logger.info).toHaveBeenCalledWith('📁 Generated 2 new handler(s)');
    });

    it('should not generate when MSW not initialized', async () => {
      (fs.exists as any).mockResolvedValue(false);

      await generateMswHandlers(fs, logger, projectRoot);

      expect(logger.error).toHaveBeenCalledWith('MSW not initialized. Run "ruch msw init" first.');
      expect(fs.writeFile).not.toHaveBeenCalled();
    });

    it('should warn when no domains exist', async () => {
      const { getExistingDomains } = await import('../../src/utils/domain-analyzer');
      
      (fs.exists as any).mockResolvedValue(true);
      (getExistingDomains as any).mockResolvedValue([]);

      await generateMswHandlers(fs, logger, projectRoot);

      expect(logger.warning).toHaveBeenCalledWith('No domains found. Create domains first with "ruch create <domain-name>".');
    });

    it('should skip existing handlers', async () => {
      const { getExistingDomains } = await import('../../src/utils/domain-analyzer');
      
      const mockDomains = [{ name: 'user', path: '/test/domains/user' }];

      (fs.exists as any).mockImplementation((filePath: string) => {
        if (filePath === mswPath) return Promise.resolve(true);
        if (filePath.includes('user.ts')) return Promise.resolve(true);
        return Promise.resolve(false);
      });
      (getExistingDomains as any).mockResolvedValue(mockDomains);

      await generateMswHandlers(fs, logger, projectRoot);

      expect(logger.info).toHaveBeenCalledWith('⏭️  Handler already exists: user.ts');
      expect(logger.info).toHaveBeenCalledWith('📁 Generated 0 new handler(s)');
    });

    it('should handle errors', async () => {
      const { getExistingDomains } = await import('../../src/utils/domain-analyzer');
      
      (fs.exists as any).mockResolvedValue(true);
      (getExistingDomains as any).mockRejectedValue(new Error('Analysis failed'));

      await generateMswHandlers(fs, logger, projectRoot);

      expect(logger.error).toHaveBeenCalledWith('Error generating MSW handlers: Analysis failed');
    });
  });

  describe('generateMswMocks', () => {
    it('should generate mock data for all domains', async () => {
      const { getExistingDomains } = await import('../../src/utils/domain-analyzer');
      const { getMswMocksTemplate } = await import('../../src/templates/msw');
      
      const mockDomains = [
        { name: 'user', path: '/test/domains/user' },
        { name: 'product', path: '/test/domains/product' }
      ];

      (fs.exists as any).mockImplementation((filePath: string) => {
        if (filePath === mswPath) return Promise.resolve(true);
        if (filePath.includes('mockData.ts')) return Promise.resolve(false);
        return Promise.resolve(false);
      });
      (fs.ensureDir as any).mockResolvedValue(undefined);
      (fs.writeFile as any).mockResolvedValue(undefined);
      (getExistingDomains as any).mockResolvedValue(mockDomains);
      (getMswMocksTemplate as any).mockReturnValue('// mock data');

      await generateMswMocks(fs, logger, projectRoot);

      expect(fs.writeFile).toHaveBeenCalledWith(
        path.join(srcPath, 'domains', 'user', 'mocks', 'mockData.ts'),
        '// mock data',
        'utf8'
      );
      expect(fs.writeFile).toHaveBeenCalledWith(
        path.join(srcPath, 'domains', 'product', 'mocks', 'mockData.ts'),
        '// mock data',
        'utf8'
      );
      expect(logger.success).toHaveBeenCalledWith('Mock data generated successfully!');
      expect(logger.info).toHaveBeenCalledWith('📁 Generated 2 new mock file(s)');
    });

    it('should not generate when MSW not initialized', async () => {
      (fs.exists as any).mockResolvedValue(false);

      await generateMswMocks(fs, logger, projectRoot);

      expect(logger.error).toHaveBeenCalledWith('MSW not initialized. Run "ruch msw init" first.');
      expect(fs.writeFile).not.toHaveBeenCalled();
    });

    it('should skip existing mock files', async () => {
      const { getExistingDomains } = await import('../../src/utils/domain-analyzer');
      
      const mockDomains = [{ name: 'user', path: '/test/domains/user' }];

      (fs.exists as any).mockImplementation((filePath: string) => {
        if (filePath === mswPath) return Promise.resolve(true);
        if (filePath.includes('mockData.ts')) return Promise.resolve(true);
        return Promise.resolve(false);
      });
      (getExistingDomains as any).mockResolvedValue(mockDomains);

      await generateMswMocks(fs, logger, projectRoot);

      expect(logger.info).toHaveBeenCalledWith('⏭️  Mock data already exists: user/mocks/mockData.ts');
      expect(logger.info).toHaveBeenCalledWith('📁 Generated 0 new mock file(s)');
    });

    it('should handle errors', async () => {
      const { getExistingDomains } = await import('../../src/utils/domain-analyzer');
      
      (fs.exists as any).mockResolvedValue(true);
      (getExistingDomains as any).mockRejectedValue(new Error('Mock generation failed'));

      await generateMswMocks(fs, logger, projectRoot);

      expect(logger.error).toHaveBeenCalledWith('Error generating mock data: Mock generation failed');
    });
  });

  describe('updateMswDomain', () => {
    const domainName = 'user';
    const mockAnalysis = {
      domainName: 'user',
      entities: [{ name: 'User', fields: [], filePath: '' }],
      ports: [{ name: 'UserPort', methods: [], filePath: '' }],
      adapters: [{ name: 'UserAdapter', methods: [], filePath: '' }],
      primaryEntity: { name: 'User', fields: [], filePath: '' }
    };

    it('should update domain with dynamic analysis', async () => {
      const { getExistingDomains } = await import('../../src/utils/domain-analyzer');
      const { analyzeDomain } = await import('../../src/utils/domain-file-analyzer');
      const { generateDynamicMswHandlers, generateDynamicMockData } = await import('../../src/templates/msw-dynamic');
      const { getMswHandlersIndexTemplate } = await import('../../src/templates/msw');
      
      const mockDomains = [{ name: 'user', path: '/test/domains/user' }];

      (fs.exists as any).mockImplementation((filePath: string) => {
        if (filePath === mswPath) return Promise.resolve(true);
        if (filePath.includes('user.ts') || filePath.includes('mockData.ts')) return Promise.resolve(true);
        return Promise.resolve(false);
      });
      (fs.readFile as any).mockResolvedValue('// existing content');
      (fs.writeFile as any).mockResolvedValue(undefined);
      (fs.ensureDir as any).mockResolvedValue(undefined);
      (getExistingDomains as any).mockResolvedValue(mockDomains);
      (analyzeDomain as any).mockResolvedValue(mockAnalysis);
      (generateDynamicMswHandlers as any).mockReturnValue('// dynamic handler');
      (generateDynamicMockData as any).mockReturnValue('// dynamic mock');
      (getMswHandlersIndexTemplate as any).mockReturnValue('// handlers index');

      await updateMswDomain(domainName, fs, logger, projectRoot, true);

      expect(analyzeDomain).toHaveBeenCalledWith(
        path.join(srcPath, 'domains', domainName),
        domainName
      );
      expect(generateDynamicMswHandlers).toHaveBeenCalledWith(mockAnalysis);
      expect(generateDynamicMockData).toHaveBeenCalledWith(mockAnalysis);
      expect(logger.success).toHaveBeenCalledWith('MSW configuration updated for domain "user"!');
      expect(logger.warning).toHaveBeenCalledWith('⚠️  IMPORTANT: MSW handlers are based on your Port interfaces.');
      expect(logger.warning).toHaveBeenCalledWith('⚠️  IMPORTANT: Mock data is generated from your Entity definitions.');
    });

    it('should not update when MSW not initialized', async () => {
      (fs.exists as any).mockResolvedValue(false);

      await updateMswDomain(domainName, fs, logger, projectRoot, true);

      expect(logger.error).toHaveBeenCalledWith('MSW not initialized. Run "ruch msw init" first.');
    });

    it('should handle non-existent domain', async () => {
      const { getExistingDomains } = await import('../../src/utils/domain-analyzer');
      
      (fs.exists as any).mockResolvedValue(true);
      (getExistingDomains as any).mockResolvedValue([]);

      await updateMswDomain(domainName, fs, logger, projectRoot, true);

      expect(logger.error).toHaveBeenCalledWith('Domain "user" not found. Available domains: ');
    });

    it('should fallback to generic templates on analysis error', async () => {
      const { getExistingDomains } = await import('../../src/utils/domain-analyzer');
      const { analyzeDomain } = await import('../../src/utils/domain-file-analyzer');
      const { getMswHandlersTemplate, getMswMocksTemplate } = await import('../../src/templates/msw');
      
      const mockDomains = [{ name: 'user', path: '/test/domains/user' }];

      (fs.exists as any).mockImplementation((filePath: string) => {
        if (filePath === mswPath) return Promise.resolve(true);
        if (filePath.includes('user.ts') || filePath.includes('mockData.ts')) return Promise.resolve(true);
        return Promise.resolve(false);
      });
      (fs.readFile as any).mockResolvedValue('// existing content');
      (fs.writeFile as any).mockResolvedValue(undefined);
      (fs.ensureDir as any).mockResolvedValue(undefined);
      (getExistingDomains as any).mockResolvedValue(mockDomains);
      (analyzeDomain as any).mockRejectedValue(new Error('Analysis failed'));
      (getMswHandlersTemplate as any).mockReturnValue('// generic handler');
      (getMswMocksTemplate as any).mockReturnValue('// generic mock');

      await updateMswDomain(domainName, fs, logger, projectRoot, true);

      expect(logger.warning).toHaveBeenCalledWith('⚠️  Could not analyze domain structure: Analysis failed');
      expect(logger.info).toHaveBeenCalledWith('📄 Falling back to generic templates...');
      expect(getMswHandlersTemplate).toHaveBeenCalledWith(domainName);
      expect(getMswMocksTemplate).toHaveBeenCalledWith(domainName);
    });

    it('should create backups before updating', async () => {
      const { getExistingDomains } = await import('../../src/utils/domain-analyzer');
      const { analyzeDomain } = await import('../../src/utils/domain-file-analyzer');
      
      const mockDomains = [{ name: 'user', path: '/test/domains/user' }];

      (fs.exists as any).mockImplementation((filePath: string) => {
        if (filePath === mswPath) return Promise.resolve(true);
        if (filePath.includes('user.ts') || filePath.includes('mockData.ts')) return Promise.resolve(true);
        return Promise.resolve(false);
      });
      (fs.readFile as any).mockResolvedValue('// existing content');
      (fs.writeFile as any).mockResolvedValue(undefined);
      (getExistingDomains as any).mockResolvedValue(mockDomains);
      (analyzeDomain as any).mockResolvedValue(mockAnalysis);

      await updateMswDomain(domainName, fs, logger, projectRoot, true);

      // Check that backups were created
      const writeFileCalls = (fs.writeFile as any).mock.calls;
      const backupCalls = writeFileCalls.filter((call: any[]) => call[0].includes('.backup.'));
      expect(backupCalls.length).toBeGreaterThan(0);
      expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('📁 Handler backup:'));
      expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('📁 Mock data backup:'));
    });

    it('should handle errors', async () => {
      const { getExistingDomains } = await import('../../src/utils/domain-analyzer');
      
      (fs.exists as any).mockResolvedValue(true);
      (getExistingDomains as any).mockRejectedValue(new Error('Update failed'));

      await updateMswDomain(domainName, fs, logger, projectRoot, true);

      expect(logger.error).toHaveBeenCalledWith('Error updating MSW for domain "user": Update failed');
    });
  });

  describe('updateMsw', () => {
    it('should update MSW with missing handlers', async () => {
      const { getExistingDomains } = await import('../../src/utils/domain-analyzer');
      const { getMswHandlersTemplate, getMswSetupTemplate, getMswBrowserTemplate, getMswServerTemplate, getMswHandlersIndexTemplate } = await import('../../src/templates/msw');
      
      const mockDomains = [
        { name: 'user', path: '/test/domains/user' },
        { name: 'product', path: '/test/domains/product' }
      ];

      (fs.exists as any).mockImplementation((filePath: string) => {
        if (filePath === mswPath) return Promise.resolve(true);
        if (filePath.includes('user.ts')) return Promise.resolve(true);
        if (filePath.includes('product.ts')) return Promise.resolve(false);
        if (filePath.includes('setup.ts') || filePath.includes('browser.ts') || filePath.includes('server.ts')) return Promise.resolve(true);
        return Promise.resolve(false);
      });
      (fs.readFile as any).mockResolvedValue('// existing content');
      (fs.writeFile as any).mockResolvedValue(undefined);
      (fs.ensureDir as any).mockResolvedValue(undefined);
      (getExistingDomains as any).mockResolvedValue(mockDomains);
      (getMswHandlersTemplate as any).mockReturnValue('// handler');
      (getMswSetupTemplate as any).mockReturnValue('// setup');
      (getMswBrowserTemplate as any).mockReturnValue('// browser');
      (getMswServerTemplate as any).mockReturnValue('// server');
      (getMswHandlersIndexTemplate as any).mockReturnValue('// handlers index');

      await updateMsw(fs, logger, projectRoot, true);

      expect(logger.info).toHaveBeenCalledWith('Found 1 missing handler(s):');
      expect(logger.info).toHaveBeenCalledWith('  - product');
      expect(fs.writeFile).toHaveBeenCalledWith(
        path.join(handlersPath, 'product.ts'),
        '// handler',
        'utf8'
      );
      expect(logger.success).toHaveBeenCalledWith('MSW configuration updated successfully!');
      expect(logger.info).toHaveBeenCalledWith('Added 1 handler(s) to MSW configuration');
    });

    it('should not update when MSW not initialized', async () => {
      (fs.exists as any).mockResolvedValue(false);

      await updateMsw(fs, logger, projectRoot, true);

      expect(logger.error).toHaveBeenCalledWith('MSW not initialized. Run "ruch msw init" first.');
    });

    it('should show message when no missing handlers', async () => {
      const { getExistingDomains } = await import('../../src/utils/domain-analyzer');
      
      const mockDomains = [{ name: 'user', path: '/test/domains/user' }];

      (fs.exists as any).mockImplementation((filePath: string) => {
        if (filePath === mswPath) return Promise.resolve(true);
        if (filePath.includes('user.ts')) return Promise.resolve(true);
        return Promise.resolve(false);
      });
      (getExistingDomains as any).mockResolvedValue(mockDomains);

      await updateMsw(fs, logger, projectRoot, true);

      expect(logger.info).toHaveBeenCalledWith('MSW configuration is already up to date. No missing handlers found.');
    });

    it('should handle errors', async () => {
      const { getExistingDomains } = await import('../../src/utils/domain-analyzer');
      
      (fs.exists as any).mockResolvedValue(true);
      (getExistingDomains as any).mockRejectedValue(new Error('Global update failed'));

      await updateMsw(fs, logger, projectRoot, true);

      expect(logger.error).toHaveBeenCalledWith('Error updating MSW: Global update failed');
    });
  });
}); 