import { describe, it, expect, beforeEach, vi } from 'vitest';
import fs from 'fs-extra';
import { logger } from '../../src/utils/logger';
import * as templates from '../../src/templates/domains';
import { createDomain } from '../../src/commands/create';
import path from 'path';
import * as paths from '../../src/utils/paths';
import type { FileSystem } from '../../src/utils/file-operations';
import type { Logger } from '../../src/utils/logging';
import type { DomainGenerationOptions } from '../../src/templates/domains';

vi.mock('fs-extra', () => ({
  default: {
    exists: vi.fn(),
    ensureDir: vi.fn(),
    writeFile: vi.fn(),
    remove: vi.fn(),
  },
}));

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/domains', () => ({
  getDomainFiles: vi.fn(),
}));

vi.mock('../src/utils/paths', () => ({
  validateDomainName: vi.fn(),
  buildDomainDirectoryPath: vi.fn(),
  getDomainsSourceDirectoryPath: vi.fn(),
}));

const domainName = 'testdomain';
const domainPath = path.join(process.cwd(), 'src', 'domains', domainName);
const domainsSourcePath = path.join(process.cwd(), 'src', 'domains');

const fullOptions: DomainGenerationOptions = {
  withApi: true,
  withQueries: true,
  withStore: true,
  withUi: true,
};

const minimalOptions: DomainGenerationOptions = {
  withApi: false,
  withQueries: false,
  withStore: false,
  withUi: false,
};

describe('createDomain', () => {
  let mockFileSystem: FileSystem;
  let mockLogger: Logger;

  beforeEach(() => {
    vi.clearAllMocks();
    vi.spyOn(paths, 'buildDomainDirectoryPath').mockReturnValue(domainPath);
    vi.spyOn(paths, 'validateDomainName').mockReturnValue(true);
    vi.spyOn(templates, 'getDomainFiles').mockImplementation((domainName, options = { withUi: true, withStore: true, withQueries: true, withApi: true }) => {
      const files = [
        { path: 'entities/Testdomain.ts', content: '// entities' },
        { path: 'entities/Testdomain.test.ts', content: '// entities test' },
        { path: 'ports/TestdomainPort.ts', content: '// ports' },
        { path: 'services/TestdomainService.ts', content: '// services' },
        { path: 'services/TestdomainService.test.ts', content: '// services test' },
        { path: 'adapters/TestdomainAdapter.ts', content: '// adapters' },
        { path: 'adapters/TestdomainAdapter.test.ts', content: '// adapters test' },
        { path: 'hooks/useTestdomain.ts', content: '// hooks' },
        { path: 'hooks/useTestdomain.test.ts', content: '// hooks test' }
      ];
      if (options.withUi) {
        files.push({ path: 'ui/TestdomainView.tsx', content: '// ui' });
        files.push({ path: 'ui/TestdomainView.test.tsx', content: '// ui test' });
      }
      return files;
    });

    // Mocks FileSystem
    mockFileSystem = {
      exists: vi.fn(),
      pathExists: vi.fn(),
      ensureDir: vi.fn(),
      writeFile: vi.fn(),
      remove: vi.fn(),
      readFile: vi.fn(),
      readdir: vi.fn(),
      stat: vi.fn(),
      mkdir: vi.fn(),
    } as unknown as FileSystem;
    // Mocks Logger
    mockLogger = {
      error: vi.fn(),
      success: vi.fn(),
      info: vi.fn(),
      warning: vi.fn(),
      log: vi.fn(),
    };
  });

  it('should create a domain with all modules', async () => {
    const existsMock = vi.fn().mockResolvedValue(false);
    const ensureDirMock = vi.fn().mockResolvedValue(undefined);
    const writeFileMock = vi.fn().mockResolvedValue(undefined);

    vi.mocked(mockFileSystem.exists).mockImplementation(existsMock);
    vi.mocked(mockFileSystem.ensureDir).mockImplementation(ensureDirMock);
    vi.mocked(mockFileSystem.writeFile).mockImplementation(writeFileMock);

    await createDomain(domainName, {
      withUi: true,
      withStore: true,
      withQueries: true,
      withApi: true
    }, mockFileSystem, mockLogger);

    const expectedDirs = [
      domainPath,
      path.join(domainPath, 'entities'),
      path.join(domainPath, 'ports'),
      path.join(domainPath, 'services'),
      path.join(domainPath, 'adapters'),
      path.join(domainPath, 'hooks'),
      path.join(domainPath, 'ui')
    ];

    const ensureDirCalls = vi.mocked(mockFileSystem.ensureDir).mock.calls.map(call => call[0]);
    expectedDirs.forEach(dir => {
      expect(ensureDirCalls).toContain(dir);
    });

    const expectedFiles = [
      path.join(domainPath, 'entities', 'Testdomain.ts'),
      path.join(domainPath, 'entities', 'Testdomain.test.ts'),
      path.join(domainPath, 'ports', 'TestdomainPort.ts'),
      path.join(domainPath, 'services', 'TestdomainService.ts'),
      path.join(domainPath, 'services', 'TestdomainService.test.ts'),
      path.join(domainPath, 'adapters', 'TestdomainAdapter.ts'),
      path.join(domainPath, 'adapters', 'TestdomainAdapter.test.ts'),
      path.join(domainPath, 'hooks', 'useTestdomain.ts'),
      path.join(domainPath, 'hooks', 'useTestdomain.test.ts'),
      path.join(domainPath, 'ui', 'TestdomainView.tsx'),
      path.join(domainPath, 'ui', 'TestdomainView.test.tsx')
    ];

    expectedFiles.forEach(file => {
      expect(mockFileSystem.writeFile).toHaveBeenCalledWith(file, expect.any(String), 'utf8');
    });

    expect(mockLogger.success).toHaveBeenCalledWith(`Domain "${domainName}" created successfully!`);
  });

  it('should create a domain with minimal structure', async () => {
    const existsMock = vi.fn().mockResolvedValue(false);
    const ensureDirMock = vi.fn().mockResolvedValue(undefined);
    const writeFileMock = vi.fn().mockResolvedValue(undefined);

    vi.mocked(mockFileSystem.exists).mockImplementation(existsMock);
    vi.mocked(mockFileSystem.ensureDir).mockImplementation(ensureDirMock);
    vi.mocked(mockFileSystem.writeFile).mockImplementation(writeFileMock);

    await createDomain(domainName, {
      withUi: false,
      withStore: false,
      withQueries: false,
      withApi: false
    }, mockFileSystem, mockLogger);

    const expectedDirs = [
      domainPath,
      path.join(domainPath, 'entities'),
      path.join(domainPath, 'ports'),
      path.join(domainPath, 'services'),
      path.join(domainPath, 'adapters'),
      path.join(domainPath, 'hooks')
    ];

    const ensureDirCalls = vi.mocked(mockFileSystem.ensureDir).mock.calls.map(call => call[0]);
    expectedDirs.forEach(dir => {
      expect(ensureDirCalls).toContain(dir);
    });

    const expectedFiles = [
      path.join(domainPath, 'entities', 'Testdomain.ts'),
      path.join(domainPath, 'entities', 'Testdomain.test.ts'),
      path.join(domainPath, 'ports', 'TestdomainPort.ts'),
      path.join(domainPath, 'services', 'TestdomainService.ts'),
      path.join(domainPath, 'services', 'TestdomainService.test.ts'),
      path.join(domainPath, 'adapters', 'TestdomainAdapter.ts'),
      path.join(domainPath, 'adapters', 'TestdomainAdapter.test.ts'),
      path.join(domainPath, 'hooks', 'useTestdomain.ts'),
      path.join(domainPath, 'hooks', 'useTestdomain.test.ts')
    ];

    expectedFiles.forEach(file => {
      expect(mockFileSystem.writeFile).toHaveBeenCalledWith(file, expect.any(String), 'utf8');
    });

    // Vérifier que les fichiers UI ne sont pas créés
    const uiFiles = [
      path.join(domainPath, 'ui', 'TestdomainView.tsx'),
      path.join(domainPath, 'ui', 'TestdomainView.test.tsx')
    ];

    uiFiles.forEach(file => {
      const matchingCall = vi.mocked(mockFileSystem.writeFile).mock.calls.find(
        (call) => call[0] === file
      );
      expect(matchingCall).toBeUndefined();
    });

    expect(mockLogger.success).toHaveBeenCalledWith(`Domain "${domainName}" created successfully!`);
  });

  it('should not create domain if it already exists', async () => {
    (mockFileSystem.exists as any).mockResolvedValue(true);

    await createDomain(domainName, fullOptions, mockFileSystem, mockLogger);

    expect(mockFileSystem.ensureDir).not.toHaveBeenCalled();
    expect(mockFileSystem.writeFile).not.toHaveBeenCalled();
    expect(mockLogger.error).toHaveBeenCalledWith(`Domain "${domainName}" already exists at ${domainPath}`);
  });

  it('should not create domain if name is invalid', async () => {
    (paths.validateDomainName as any).mockReturnValue(false);

    await createDomain(domainName, fullOptions, mockFileSystem, mockLogger);

    expect(mockFileSystem.ensureDir).not.toHaveBeenCalled();
    expect(mockFileSystem.writeFile).not.toHaveBeenCalled();
    expect(mockLogger.error).toHaveBeenCalledWith(`❌ Invalid domain name: "${domainName}"`);
  });

  it('should clean up on error', async () => {
    (mockFileSystem.exists as any)
      .mockResolvedValueOnce(false) // avant création
      .mockResolvedValueOnce(true); // après l'erreur, pour cleanup
    (mockFileSystem.ensureDir as any).mockResolvedValue(undefined);
    (mockFileSystem.writeFile as any).mockRejectedValue(new Error('Test error'));

    await createDomain(domainName, fullOptions, mockFileSystem, mockLogger);

    expect(mockFileSystem.remove).toHaveBeenCalledWith(domainPath);
    expect(mockLogger.error).toHaveBeenCalledWith('Error creating domain: Test error');
    expect(mockLogger.info).toHaveBeenCalledWith('Cleanup completed after error.');
  });
});