'use strict';

const fs = require('fs-extra');
const path = require('canonical-path');
const shelljs = require('shelljs');

const installer = require('./ng-packages-installer');

describe('NgPackagesInstaller', () => {
  const ngPackages = installer.ngPackages;
  const rootDir = 'root/dir';
  const absoluteRootDir = path.resolve(rootDir);
  const nodeModulesDir = `${absoluteRootDir}/node_modules`;

  beforeEach(() => {
    spyOn(fs, 'copySync');
    spyOn(fs, 'existsSync');
    spyOn(fs, 'writeFileSync');
    spyOn(shelljs, 'exec');
    spyOn(shelljs, 'rm');
  });

  // Properties

  describe('.ngPackages', () => {
    it('should include all package names', () => {
      // For example...
      expect(installer.ngPackages).toContain('common');
      expect(installer.ngPackages).toContain('core');
      expect(installer.ngPackages).toContain('router');
      expect(installer.ngPackages).toContain('upgrade');

      expect(installer.ngPackages).not.toContain('static');
      expect(installer.ngPackages).not.toContain('upgrade/static');
    });

    it('should correspond to package directories with top-level \'package.json\' files', () => {
      fs.existsSync.and.callThrough();

      const packagesDir = path.resolve(__dirname, '../../packages');

      installer.ngPackages.forEach(packageName => {
        const packageJson = `${packagesDir}/${packageName}/package.json`;
        expect(fs.existsSync(packageJson)).toBe(true);
      });
    });
  });

  // Methods

  describe('checkPackages()', () => {
    beforeEach(() => {
      spyOn(console, 'warn');
      spyOn(installer, '_findLocalPackages');
    });

    it('should check whether there are any local Angular packages in the target directory', () => {
      installer._findLocalPackages.and.returnValue([]);

      installer.checkPackages(rootDir);
      expect(installer._findLocalPackages).toHaveBeenCalledWith(absoluteRootDir);
    });

    it('should not print a warning if all Angular packages come from npm', () => {
      installer._findLocalPackages.and.returnValue([]);

      installer.checkPackages(rootDir);
      expect(console.warn).not.toHaveBeenCalled();
    });

    describe('when there are local Angular packages', () => {
      beforeEach(() => {
        installer._findLocalPackages.and.returnValue(['common', 'router']);
      });

      it('should print a warning', () => {
        installer.checkPackages(rootDir);

        expect(console.warn).toHaveBeenCalled();
        expect(console.warn.calls.mostRecent().args[0]).toContain('WARNING');
      });

      it('should list the local (i.e. overwritten) packages', () => {
        installer.checkPackages(rootDir);

        const warning = console.warn.calls.mostRecent().args[0];
        expect(warning).toContain('@angular/common');
        expect(warning).toContain('@angular/router');
        expect(warning).not.toContain('@angular/core');
        expect(warning).not.toContain('@angular/upgrade');
      });

      it('should mention the command to restore the Angular packages', () => {
        // When run for the current working directory...
        const dir1 = '.';
        const restoreCmdRe1 = RegExp('\\bnode .*?ng-packages-installer restore \\.');

        installer.checkPackages(dir1);

        expect(console.warn.calls.argsFor(0)[0]).toMatch(restoreCmdRe1);

        // When run for a different directory...
        const dir2 = rootDir;
        const restoreCmdRe2 = RegExp(`\\bnode .*?ng-packages-installer restore .*?${path.normalize(rootDir)}\\b`);

        installer.checkPackages(dir2);

        expect(console.warn.calls.argsFor(1)[0]).toMatch(restoreCmdRe2);
      });
    });
  });

  describe('overwritePackages()', () => {
    beforeEach(() => {
      spyOn(installer, '_overwritePackage');
    });

    it('should override the Angular packages in the target directory with the locally built ones', () => {
      installer.overwritePackages(rootDir);
      expect(installer._overwritePackage).toHaveBeenCalledTimes(ngPackages.length);

      ngPackages.forEach(packageName =>
        expect(installer._overwritePackage).toHaveBeenCalledWith(packageName, nodeModulesDir));
    });
  });

  describe('restorePackages()', () => {
    beforeEach(() => {
      spyOn(installer, '_findLocalPackages');
      spyOn(installer, '_reinstallOverwrittenNodeModules');
    });

    it('should check whether there are any local Angular packages in the target directory first', () => {
      installer._findLocalPackages.and.callFake(() => {
        expect(installer._reinstallOverwrittenNodeModules).not.toHaveBeenCalled();
        return [];
      });

      installer.restorePackages(rootDir);
      expect(installer._findLocalPackages).toHaveBeenCalledWith(absoluteRootDir);
    });

    it('should re-install dependencies from npm afterwards (if necessary)', () => {
      // No local packages.
      installer._findLocalPackages.and.returnValue([]);

      installer.restorePackages(rootDir);
      expect(installer._reinstallOverwrittenNodeModules).not.toHaveBeenCalled();

      // All local packages.
      installer._reinstallOverwrittenNodeModules.calls.reset();
      installer._findLocalPackages.and.returnValue(ngPackages);

      installer.restorePackages(rootDir);
      expect(installer._reinstallOverwrittenNodeModules).toHaveBeenCalledWith(absoluteRootDir);

      // Some local packages.
      installer._reinstallOverwrittenNodeModules.calls.reset();
      installer._findLocalPackages.and.returnValue(['common', 'core', 'router', 'upgrade']);

      installer.restorePackages(rootDir);
      expect(installer._reinstallOverwrittenNodeModules).toHaveBeenCalledWith(absoluteRootDir);
    });
  });

  describe('_findLocalPackages()', () => {
    beforeEach(() => {
      spyOn(installer, '_isLocalPackage');
    });

    it('should check all Angular packages', () => {
      installer._findLocalPackages(absoluteRootDir);

      ngPackages.forEach(packageName =>
        expect(installer._isLocalPackage).toHaveBeenCalledWith(packageName, nodeModulesDir));
    });

    it('should return an empty list if all Angular packages come from npm', () => {
      installer._isLocalPackage.and.returnValue(false);
      expect(installer._findLocalPackages(rootDir)).toEqual([]);
    });

    it('should return a list of all local (i.e. overwritten) Angular packages', () => {
      const localPackages = ['common', 'core', 'router', 'upgrade'];

      installer._isLocalPackage.and.callFake(packageName => localPackages.includes(packageName));

      expect(installer._findLocalPackages(rootDir)).toEqual(localPackages);
    });
  });

  describe('_isLocalPackage()', () => {
    it('should check whether the specified package is local/overwritten', () => {
      const targetPackageDir = `${rootDir}/@angular/somePackage`;
      const localFlagFile = `${targetPackageDir}/.ng-local`;

      installer._isLocalPackage('somePackage', rootDir);
      expect(fs.existsSync).toHaveBeenCalledWith(localFlagFile);
    });

    it('should return whether the specified package was local', () => {
      fs.existsSync.and.returnValues(true, false);

      expect(installer._isLocalPackage('somePackage', rootDir)).toBe(true);
      expect(installer._isLocalPackage('somePackage', rootDir)).toBe(false);
    });
  });

  describe('_log()', () => {
    beforeEach(() => {
      spyOn(console, 'info');
    });

    afterEach(() => {
      installer.debug = false;
    });

    it('should log a message to the console if the `debug` property is true', () => {
      installer._log('foo');
      expect(console.info).not.toHaveBeenCalled();

      installer.debug = true;
      installer._log('bar');
      expect(console.info).toHaveBeenCalledWith('  [NgPackagesInstaller]: bar');
    });
  });

  describe('_overwritePackage()', () => {
    beforeEach(() => {
      fs.existsSync.and.returnValue(true);
    });

    it('should check whether the Angular package is installed', () => {
      const targetPackageDir = `${rootDir}/@angular/somePackage`;

      installer._overwritePackage('somePackage', rootDir);
      expect(fs.existsSync).toHaveBeenCalledWith(targetPackageDir);
    });

    it('should remove the original package from the target directory', () => {
      const targetPackageDir = `${rootDir}/@angular/somePackage`;

      shelljs.rm.and.callFake(() => expect(fs.existsSync).toHaveBeenCalled());

      installer._overwritePackage('somePackage', rootDir);
      expect(shelljs.rm).toHaveBeenCalledWith('-rf', targetPackageDir);
    });

    it('should copy the source package directory to the target directory', () => {
      const sourcePackageDir = path.resolve(__dirname, '../../dist/packages-dist/somePackage');
      const targetPackageDir = `${rootDir}/@angular/somePackage`;

      fs.copySync.and.callFake(() => expect(shelljs.rm).toHaveBeenCalled());

      installer._overwritePackage('somePackage', rootDir);
      expect(fs.copySync).toHaveBeenCalledWith(sourcePackageDir, targetPackageDir);
    });

    it('should add an empty `.ng-local` file to the target directory', () => {
      const targetPackageDir = `${rootDir}/@angular/somePackage`;
      const localFlagFile = `${targetPackageDir}/.ng-local`;

      fs.writeFileSync.and.callFake(() => expect(fs.copySync).toHaveBeenCalled());

      installer._overwritePackage('somePackage', rootDir);
      expect(fs.writeFileSync).toHaveBeenCalledWith(localFlagFile, '');
    });

    it('should do nothing if the Angular package is not installed', () => {
      fs.existsSync.and.returnValue(false);

      installer._overwritePackage('somePackage', rootDir);

      expect(shelljs.rm).not.toHaveBeenCalled();
      expect(fs.copySync).not.toHaveBeenCalled();
      expect(fs.writeFileSync).not.toHaveBeenCalled();
    });
  });

  describe('_reinstallOverwrittenNodeModules()', () => {
    it('should run `yarn install --check-files` in the specified directory', () => {
      installer._reinstallOverwrittenNodeModules(rootDir);
      expect(shelljs.exec).toHaveBeenCalledWith('yarn install --check-files', {cwd: rootDir});
    });
  });
});