import componentInit from './component-init';
import fs from 'fs-extra';
import * as logger from '@squiz/dx-logger-lib';
import path from 'path';

jest.mock('fs-extra');
jest.mock('path', () => {
  return {
    ...jest.requireActual('path'),
  };
});

const fsMocked = jest.mocked(fs);

beforeEach(() => {
  jest.resetAllMocks();
  fsMocked.readJSON.mockResolvedValue({});

  // Reset our process.env.NODE_ENV variable
  process.env.NODE_ENV = 'test';
});

describe('componentInit', () => {
  it('should setup a new logger instance with the correct options', async () => {
    const loggerSpy = jest.spyOn(logger, 'getLogger');

    // Mock the process.env.NODE_ENV variable to be "development"
    process.env.NODE_ENV = 'development';

    await componentInit({
      componentType: 'basic',
      destination: '',
    });

    expect(loggerSpy).toHaveBeenCalledWith({
      name: 'component-dev',
      format: 'human',
      silent: false,
    });
  });

  it('path.resolve should be called with __dirname to get the current script directory', async () => {
    const pathResolveSpy = jest.spyOn(path, 'resolve');

    await componentInit({
      componentType: 'basic',
      destination: '',
    });

    expect(pathResolveSpy).toHaveBeenCalledWith(__dirname);
  });

  it('path.join should be called with the current script directory and the component type to get the source folder', async () => {
    const pathJoinSpy = jest.spyOn(path, 'join');

    jest
      .spyOn(path, 'resolve')
      .mockReturnValue('/Users/username/Projects/squiz/component-cli-lib/packages/component-cli-lib/src');

    await componentInit({
      componentType: 'basic',
      destination: '',
    });

    expect(pathJoinSpy).toHaveBeenCalledWith(
      '/Users/username/Projects/squiz/component-cli-lib/packages/component-cli-lib/src',
      'templates',
      'basic',
    );
  });

  it('path.resolve should be called with the destination folder to get the destination folder', async () => {
    const pathResolveSpy = jest.spyOn(path, 'resolve');

    jest.spyOn(path, 'resolve').mockReturnValue('/Users/username/Projects/squiz/new-component');

    await componentInit({
      componentType: 'basic',
      destination: '/Users/username/Projects/squiz/new-component',
    });

    expect(pathResolveSpy).toHaveBeenCalledWith('/Users/username/Projects/squiz/new-component');
  });

  it('should fail if the destination folder already exists and there are files in it', async () => {
    // Mock the current script directory
    jest.spyOn(path, 'resolve').mockImplementationOnce(() => {
      return '/Users/username/Projects/squiz/component-cli-lib/packages/component-cli-lib/src';
    });

    // Mock the destination folder
    jest.spyOn(path, 'resolve').mockImplementationOnce(() => {
      return '/Users/username/Projects/squiz/new-component';
    });

    // Mock the fs.pathExists function to return true
    jest.spyOn(fs, 'pathExists').mockImplementationOnce(() => {
      return Promise.resolve(true);
    });

    // Mock the fs.readdirSync function to return an array of files
    jest.spyOn(fs, 'readdir').mockImplementationOnce(() => {
      return Promise.resolve(['file1.txt', 'file2.txt']);
    });

    await expect(
      componentInit({
        componentType: 'basic',
        destination: '/Users/username/Projects/squiz/new-component',
      }),
    ).rejects.toThrowError('The destination folder already exists and is not empty.');
  });

  it('should create the destination folder if it does not exist', async () => {
    // Mock the current script directory
    jest.spyOn(path, 'resolve').mockImplementationOnce(() => {
      return '/Users/username/Projects/squiz/component-cli-lib/packages/component-cli-lib/src';
    });

    // Mock the destination folder
    jest.spyOn(path, 'resolve').mockImplementationOnce(() => {
      return '/Users/username/Projects/squiz/new-component';
    });

    // Mock the fs.pathExists function to return false
    jest.spyOn(fs, 'pathExists').mockImplementationOnce(() => {
      return Promise.resolve(false);
    });

    await componentInit({
      componentType: 'basic',
      destination: '/Users/username/Projects/squiz/new-component',
    });

    expect(fs.mkdirp).toHaveBeenCalledWith('/Users/username/Projects/squiz/new-component');
  });

  it('should not fail if the destination folder already exists and there are no files in it', async () => {
    // Mock the current script directory
    jest.spyOn(path, 'resolve').mockImplementationOnce(() => {
      return '/Users/username/Projects/squiz/component-cli-lib/packages/component-cli-lib/src';
    });

    // Mock the destination folder
    jest.spyOn(path, 'resolve').mockImplementationOnce(() => {
      return '/Users/username/Projects/squiz/new-component';
    });

    // Mock the fs.pathExists function to return true
    jest.spyOn(fs, 'pathExists').mockImplementationOnce(() => {
      return Promise.resolve(true);
    });

    // Mock the fs.readdir function to return an empty array
    jest.spyOn(fs, 'readdir').mockImplementationOnce(() => {
      return Promise.resolve([]);
    });

    await expect(
      componentInit({
        componentType: 'basic',
        destination: '/Users/username/Projects/squiz/new-component',
      }),
    ).resolves.toBeUndefined();
  });

  it('fs.copy should be called with the source folder and the destination folder', async () => {
    // Mock the fs.copy function
    const fsCopySpy = jest.spyOn(fs, 'copy');

    // Mock the current script directory
    jest.spyOn(path, 'resolve').mockImplementationOnce(() => {
      return '/Users/username/Projects/squiz/component-cli-lib/packages/component-cli-lib/src';
    });

    // Mock the source folder
    jest.spyOn(path, 'join').mockImplementationOnce(() => {
      return '/Users/username/Projects/squiz/component-cli-lib/packages/component-cli-lib/templates/basic';
    });

    // Mock the destination folder
    jest.spyOn(path, 'resolve').mockImplementationOnce(() => {
      return '/Users/username/Projects/squiz/new-component';
    });

    // Mock the fs.pathExists function to return true
    jest.spyOn(fs, 'pathExists').mockImplementationOnce(() => {
      return Promise.resolve(true);
    });

    // Mock the fs.readdir function to return an empty array
    jest.spyOn(fs, 'readdir').mockImplementationOnce(() => {
      return Promise.resolve([]);
    });

    await componentInit({
      componentType: 'basic',
      destination: '/Users/username/Projects/squiz/new-component',
    });

    expect(fsCopySpy).toHaveBeenCalledWith(
      '/Users/username/Projects/squiz/component-cli-lib/packages/component-cli-lib/templates/basic',
      '/Users/username/Projects/squiz/new-component',
    );
  });

  describe('prompts', () => {
    it('should default when no prompts provided', async () => {
      await componentInit({ componentType: 'basic', destination: '' });

      expect(fsMocked.writeJSON).toHaveBeenCalledWith(
        expect.any(String),
        expect.objectContaining({
          name: 'default',
          namespace: 'default-namespace',
          displayName: 'Default',
        }),
        expect.objectContaining({ spaces: 2 }),
      );
    });

    it('should overwrite default values with provided values', async () => {
      await componentInit({
        componentType: 'basic',
        destination: '',
        prompts: {
          name: 'my-component',
          namespace: 'my-namespace',
          displayName: 'My Component',
        },
      });

      expect(fsMocked.writeJSON).toHaveBeenCalledWith(
        expect.any(String),
        expect.objectContaining({
          name: 'my-component',
          namespace: 'my-namespace',
          displayName: 'My Component',
        }),
        expect.objectContaining({ spaces: 2 }),
      );
    });

    it('should read and write the destination manifest file', async () => {
      await componentInit({
        componentType: 'basic',
        destination: '/Users/username/Projects/squiz/new-component',
      });

      expect(fsMocked.readJSON).toHaveBeenCalledWith('/Users/username/Projects/squiz/new-component/manifest.json');
      expect(fsMocked.writeJSON).toHaveBeenCalledWith(
        '/Users/username/Projects/squiz/new-component/manifest.json',
        expect.any(Object),
        expect.objectContaining({ spaces: 2 }),
      );
    });
  });
});
