import { describe, it, expect, beforeEach, vi } from 'vitest';
import fs from 'fs-extra';
import { logger } from '../../src/utils/logger';
import { listDomains } from '../../src/commands/list';
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';

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

describe('listDomains', () => {
  const domainsSourcePath = path.join(process.cwd(), 'src', 'domains');
  const mockFileSystem: FileSystem = fs;
  const mockLogger: Logger = logger;

  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(paths.getDomainsSourceDirectoryPath).mockReturnValue(domainsSourcePath);
  });

  it('should list all domains when domains exist', async () => {
    const domains = ['domain1', 'domain2', 'domain3'];
    const existsMock = vi.fn().mockResolvedValue(true);
    const readdirMock = vi.fn().mockResolvedValue(domains);
    const statMock = vi.fn().mockResolvedValue({
      isDirectory: () => true
    } as fs.Stats);

    vi.mocked(fs.exists).mockImplementation(existsMock);
    vi.mocked(fs.readdir).mockImplementation(readdirMock);
    vi.mocked(fs.stat).mockImplementation(statMock);

    await listDomains(mockFileSystem, mockLogger);

    expect(fs.exists).toHaveBeenCalledWith(domainsSourcePath);
    expect(fs.readdir).toHaveBeenCalledWith(domainsSourcePath);
    expect(logger.success).toHaveBeenCalledWith(`${domains.length} domain(s) found:`);
    expect(logger.info).toHaveBeenCalledWith(`Total: ${domains.length} domain(s)`);
    expect(logger.info).toHaveBeenCalledWith(`Location: ${domainsSourcePath}`);
    domains.forEach(domain => {
      expect(logger.log).toHaveBeenCalledWith(expect.stringContaining(domain));
    });
  });

  it('should show message when no domains exist', async () => {
    const existsMock = vi.fn().mockResolvedValue(true);
    const readdirMock = vi.fn().mockResolvedValue([]);

    vi.mocked(fs.exists).mockImplementation(existsMock);
    vi.mocked(fs.readdir).mockImplementation(readdirMock);

    await listDomains(mockFileSystem, mockLogger);

    expect(fs.exists).toHaveBeenCalledWith(domainsSourcePath);
    expect(fs.readdir).toHaveBeenCalledWith(domainsSourcePath);
    expect(logger.info).toHaveBeenCalledWith('No domains found in src/domains.');
    expect(logger.info).toHaveBeenCalledWith('💡 Use "ruch create <domain-name>" to create your first domain.');
  });

  it('should handle errors when reading directory', async () => {
    const error = new Error('Failed to read directory');
    const existsMock = vi.fn().mockResolvedValue(true);
    const readdirMock = vi.fn().mockRejectedValue(error);

    vi.mocked(fs.exists).mockImplementation(existsMock);
    vi.mocked(fs.readdir).mockImplementation(readdirMock);

    await listDomains(mockFileSystem, mockLogger);

    expect(fs.exists).toHaveBeenCalledWith(domainsSourcePath);
    expect(logger.error).toHaveBeenCalledWith(`Error reading domains: ${error.message}`);
  });

  it('should only list directories', async () => {
    const files = ['domain1', 'domain2', 'file.txt', 'README.md'];
    const existsMock = vi.fn().mockResolvedValue(true);
    const readdirMock = vi.fn().mockResolvedValue(files);
    const statMock = vi.fn().mockImplementation(async (p) => ({
      isDirectory: () => typeof p === 'string' && p.includes('domain')
    } as fs.Stats));

    vi.mocked(fs.exists).mockImplementation(existsMock);
    vi.mocked(fs.readdir).mockImplementation(readdirMock);
    vi.mocked(fs.stat).mockImplementation(statMock);

    await listDomains(mockFileSystem, mockLogger);

    expect(fs.exists).toHaveBeenCalledWith(domainsSourcePath);
    expect(logger.success).toHaveBeenCalledWith('4 domain(s) found:');
    expect(logger.info).toHaveBeenCalledWith('Total: 4 domain(s)');
    expect(logger.info).toHaveBeenCalledWith(`Location: ${domainsSourcePath}`);
    expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('domain1'));
    expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('domain2'));
  });

  it('should show message when domains folder does not exist', async () => {
    const existsMock = vi.fn().mockResolvedValue(false);

    vi.mocked(fs.exists).mockImplementation(existsMock);

    await listDomains(mockFileSystem, mockLogger);

    expect(fs.exists).toHaveBeenCalledWith(domainsSourcePath);
    expect(logger.info).toHaveBeenCalledWith('No domains found in src/domains.');
    expect(logger.info).toHaveBeenCalledWith('💡 Use "ruch create <domain-name>" to create your first domain.');
  });

  it('should sort folders in logical order', async () => {
    const domain = 'test-domain';
    const folders = ['hooks', 'ui', 'services', 'adapters', 'entities', 'ports'];
    const existsMock = vi.fn().mockResolvedValue(true);
    const readdirMock = vi.fn().mockResolvedValue([domain]);
    const statMock = vi.fn().mockResolvedValue({
      isDirectory: () => true
    } as fs.Stats);
    const domainReaddirMock = vi.fn().mockResolvedValue(folders);
    const domainStatMock = vi.fn().mockResolvedValue({
      isDirectory: () => true
    } as fs.Stats);

    vi.mocked(fs.exists).mockImplementation(existsMock);
    vi.mocked(fs.readdir)
      .mockImplementationOnce(readdirMock)
      .mockImplementationOnce(domainReaddirMock);
    vi.mocked(fs.stat)
      .mockImplementationOnce(statMock)
      .mockImplementation(domainStatMock);

    await listDomains(mockFileSystem, mockLogger);

    const expectedOrder = ['entities', 'ports', 'services', 'adapters', 'hooks', 'ui'];
    const logCalls = vi.mocked(logger.log).mock.calls;
    const folderLogs = logCalls
      .map(call => call?.[0] ?? '')
      .filter(log => log.includes('📂'))
      .map(log => log.split('📂')[1]?.trim() ?? '');

    expect(folderLogs).toEqual(expectedOrder);
  });
}); 