import { describe, it, expect, beforeEach, vi } from 'vitest';
import fs from 'fs-extra';
import path from 'path';
import { logger } from '../../src/utils/logger';
import { initServiceContext, generateServiceContext, updateServiceContext } from '../../src/commands/context';

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(),
  }
}));

describe('Context Commands', () => {
  const projectRoot = '/test/project';
  const contextPath = path.join(projectRoot, 'src', 'context');
  const contextFilePath = path.join(contextPath, 'ServiceContext.tsx');
  const testFilePath = path.join(contextPath, 'ServiceContext.test.tsx');
  const domainsPath = path.join(projectRoot, 'src', 'domains');

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

  describe('initServiceContext', () => {
    it('should create empty service context when none exists', async () => {
      (fs.exists as any).mockResolvedValue(false);
      (fs.ensureDir as any).mockResolvedValue(undefined);
      (fs.writeFile as any).mockResolvedValue(undefined);

      await initServiceContext(fs, logger, projectRoot);

      expect(fs.exists).toHaveBeenCalledWith(contextFilePath);
      expect(fs.ensureDir).toHaveBeenCalledWith(contextPath);
      expect(fs.writeFile).toHaveBeenCalledTimes(2);
      expect(logger.success).toHaveBeenCalledWith('Service context created successfully!');
      expect(logger.info).toHaveBeenCalledWith(`Location: ${contextFilePath}`);
    });

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

      await initServiceContext(fs, logger, projectRoot);

      expect(fs.exists).toHaveBeenCalledWith(contextFilePath);
      expect(fs.ensureDir).not.toHaveBeenCalled();
      expect(fs.writeFile).not.toHaveBeenCalled();
      expect(logger.warning).toHaveBeenCalledWith('Service context already exists');
    });

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

      await initServiceContext(fs, logger, projectRoot);

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

  describe('generateServiceContext', () => {
    it('should generate context with existing domains', async () => {
      // Mock context doesn't exist
      (fs.exists as any).mockImplementation((path: string) => {
        if (path === contextFilePath) return Promise.resolve(false);
        if (path === domainsPath) return Promise.resolve(true);
        if (path.includes('domains/user')) return Promise.resolve(true);
        if (path.includes('domains/product')) return Promise.resolve(true);
        if (path.includes('UserService.ts')) return Promise.resolve(true);
        if (path.includes('UserAdapter.ts')) return Promise.resolve(true);
        if (path.includes('ProductService.ts')) return Promise.resolve(true);
        if (path.includes('ProductAdapter.ts')) return Promise.resolve(true);
        return Promise.resolve(false);
      });

      (fs.readdir as any).mockResolvedValue(['user', 'product']);
      (fs.stat as any).mockResolvedValue({ isDirectory: () => true });
      (fs.ensureDir as any).mockResolvedValue(undefined);
      (fs.writeFile as any).mockResolvedValue(undefined);

      await generateServiceContext(fs, logger, projectRoot);

      expect(fs.writeFile).toHaveBeenCalledTimes(2);
      expect(logger.success).toHaveBeenCalledWith('Service context created successfully!');
      expect(logger.info).toHaveBeenCalledWith('Integrated 2 domain(s):');
    });

    it('should create empty context when no domains exist', async () => {
      (fs.exists as any).mockImplementation((path: string) => {
        if (path === contextFilePath) return Promise.resolve(false);
        if (path === domainsPath) return Promise.resolve(false);
        return Promise.resolve(false);
      });

      (fs.ensureDir as any).mockResolvedValue(undefined);
      (fs.writeFile as any).mockResolvedValue(undefined);

      await generateServiceContext(fs, logger, projectRoot);

      expect(logger.warning).toHaveBeenCalledWith('No domains found. Creating empty service context.');
      expect(fs.writeFile).toHaveBeenCalledTimes(2);
    });

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

      await generateServiceContext(fs, logger, projectRoot);

      expect(logger.warning).toHaveBeenCalledWith('Service context already exists. Use "context update" to add new domains.');
      expect(fs.writeFile).not.toHaveBeenCalled();
    });
  });

  describe('updateServiceContext', () => {
    it('should update context with missing domains', async () => {
      // Mock context exists
      (fs.exists as any).mockImplementation((path: string) => {
        if (path === contextFilePath) return Promise.resolve(true);
        if (path === domainsPath) return Promise.resolve(true);
        if (path.includes('domains/user')) return Promise.resolve(true);
        if (path.includes('domains/product')) return Promise.resolve(true);
        if (path.includes('UserService.ts')) return Promise.resolve(true);
        if (path.includes('UserAdapter.ts')) return Promise.resolve(true);
        if (path.includes('ProductService.ts')) return Promise.resolve(true);
        if (path.includes('ProductAdapter.ts')) return Promise.resolve(true);
        return Promise.resolve(false);
      });

      // Mock existing context with only user domain
      (fs.readFile as any).mockResolvedValue(`
        interface Services {
          userService: UserService;
        }
      `);

      (fs.readdir as any).mockResolvedValue(['user', 'product']);
      (fs.stat as any).mockResolvedValue({ isDirectory: () => true });
      (fs.writeFile as any).mockResolvedValue(undefined);

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

      // Now expects 3 calls: backup + context + test files
      expect(fs.writeFile).toHaveBeenCalledTimes(3);
      expect(logger.success).toHaveBeenCalledWith('Service context updated successfully!');
      expect(logger.info).toHaveBeenCalledWith('Added 1 domain(s) to service context');
    });

    it('should not update when context is up to date', async () => {
      // Mock context exists
      (fs.exists as any).mockImplementation((path: string) => {
        if (path === contextFilePath) return Promise.resolve(true);
        if (path === domainsPath) return Promise.resolve(true);
        if (path.includes('domains/user')) return Promise.resolve(true);
        if (path.includes('UserService.ts')) return Promise.resolve(true);
        if (path.includes('UserAdapter.ts')) return Promise.resolve(true);
        return Promise.resolve(false);
      });

      // Mock existing context with user domain
      (fs.readFile as any).mockResolvedValue(`
        interface Services {
          userService: UserService;
        }
      `);

      (fs.readdir as any).mockResolvedValue(['user']);
      (fs.stat as any).mockResolvedValue({ isDirectory: () => true });

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

      expect(fs.writeFile).not.toHaveBeenCalled();
      expect(logger.info).toHaveBeenCalledWith('Service context is already up to date. No missing domains found.');
    });

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

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

      expect(logger.error).toHaveBeenCalledWith('Service context does not exist. Use "context init" or "context generate" first.');
      expect(fs.writeFile).not.toHaveBeenCalled();
    });
  });
}); 