import { Rule, SchematicContext, Tree, noop, chain, SchematicsException } from '@angular-devkit/schematics';
import { NodeDependency, addPackageJsonDependency, NodeDependencyType, getWorkspace, getProjectFromWorkspace, addModuleImportToRootModule, getAppModulePath, InsertChange } from 'schematics-utilities';
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
import { Schema } from './schema';
import * as ts from 'typescript';
import { isImported, insertImport, addProviderToModule, getModuleFile, applyChanges } from './utils';

function addPackageJsonDependencies(options: Schema): Rule {
  return (host: Tree, context: SchematicContext) => {
    const dependencies: NodeDependency[] = [
      {
        type: NodeDependencyType.Default,
        version: '^4.0.0',
        name: '@datorama/akita'
      }
    ];

    if (options.withRouter || options.router) {
      dependencies.push({
        type: NodeDependencyType.Dev,
        version: '^3.1.3',
        name: '@datorama/akita-ng-router-store'
      });
    }

    if (options.devtools) {
      dependencies.push({
        type: NodeDependencyType.Dev,
        version: '^3.0.2',
        name: '@datorama/akita-ngdevtools'
      });
    }

    if (options.httpEntityService) {
      dependencies.push({
        type: NodeDependencyType.Default,
        version: '^1.0.0',
        name: '@datorama/akita-ng-entity-service'
      });
    }

    if (options.firebaseEntityService) {
      dependencies.push({
        type: NodeDependencyType.Default,
        version: '^1.0.0',
        name: 'akita-ng-fire'
      });
    }

    dependencies.forEach(dependency => {
      addPackageJsonDependency(host, dependency);
      context.logger.log('info', `✅️ Added "${dependency.name}" into ${dependency.type}`);
    });

    return host;
  };
}

function installPackageJsonDependencies(): Rule {
  return (host: Tree, context: SchematicContext) => {
    context.addTask(new NodePackageInstallTask());
    context.logger.log('info', `🔍 Installing packages...`);

    return host;
  };
}

function getTsSourceFile(host: Tree, path: string): ts.SourceFile {
  const buffer = host.read(path);
  if (!buffer) {
    throw new SchematicsException(`Could not read file (${path}).`);
  }
  const content = buffer.toString();
  const source = ts.createSourceFile(path, content, ts.ScriptTarget.Latest, true);

  return source;
}

function injectImports(options: Schema): Rule {
  return (host: Tree, context: SchematicContext) => {
    if (!options.router && !options.devtools && !options.httpEntityService && !options.firebaseEntityService) {
      return;
    }
    const workspace = getWorkspace(host);
    const project = getProjectFromWorkspace(
      workspace,
      // Takes the first project in case it's not provided by CLI
      options.project ? options.project : Object.keys(workspace['projects'])[0]
    );
    const modulePath = getAppModulePath(host, (project as any).architect.build.options.main);

    let moduleSource = getTsSourceFile(host, modulePath);
    let importModule = 'environment';
    let importPath = '../environments/environment';

    if (!isImported(moduleSource, importModule, importPath)) {
      const change = insertImport(moduleSource, modulePath, importModule, importPath);

      if (change) {
        const recorder = host.beginUpdate(modulePath);
        recorder.insertLeft((change as InsertChange).pos, (change as InsertChange).toAdd);
        host.commitUpdate(recorder);
      }
    }

    if (options.withRouter || options.router) {
      const routerChange = insertImport(moduleSource, modulePath, 'AkitaNgRouterStoreModule', '@datorama/akita-ng-router-store');
      if (routerChange) {
        const recorder = host.beginUpdate(modulePath);
        recorder.insertLeft((routerChange as InsertChange).pos, (routerChange as InsertChange).toAdd);
        host.commitUpdate(recorder);
      }
    }

    if (options.devtools) {
      const devtoolsChange = insertImport(moduleSource, modulePath, 'AkitaNgDevtools', '@datorama/akita-ngdevtools');
      if (devtoolsChange) {
        const recorder = host.beginUpdate(modulePath);
        recorder.insertLeft((devtoolsChange as InsertChange).pos, (devtoolsChange as InsertChange).toAdd);
        host.commitUpdate(recorder);
      }
    }

    if (options.httpEntityService) {
      const entityServiceChange = insertImport(moduleSource, modulePath, 'NG_ENTITY_SERVICE_CONFIG', '@datorama/akita-ng-entity-service');
      if (entityServiceChange) {
        const recorder = host.beginUpdate(modulePath);
        recorder.insertLeft((entityServiceChange as InsertChange).pos, (entityServiceChange as InsertChange).toAdd);
        host.commitUpdate(recorder);
      }
    }

    return host;
  };
}

function setSchematicsAsDefault(): Rule {
  return (host: Tree, context: SchematicContext) => {
    const exec = require('child_process').exec;

    exec('ng config cli.defaultCollection @datorama/akita', () => {
      context.logger.log('info', `✅️ Setting Akita schematics as default`);
    });
    return host;
  };
}

function addModuleToImports(options: Schema): Rule {
  return (host: Tree, context: SchematicContext) => {
    const workspace = getWorkspace(host);
    const project = getProjectFromWorkspace(
      workspace,
      // Takes the first project in case it's not provided by CLI
      options.project ? options.project : Object.keys(workspace['projects'])[0]
    );

    let importDevtools = '';
    let importRouter = '';
    let provideEntityServiceConfig = '';

    if ((options.withRouter || options.router) && options.devtools) {
      importRouter = `AkitaNgRouterStoreModule.forRoot()`;
    }

    if (options.devtools) {
      importDevtools = `environment.production ? [] : AkitaNgDevtools.forRoot()`;
    }

    if (options.httpEntityService) {
      provideEntityServiceConfig = `{ provide: NG_ENTITY_SERVICE_CONFIG, useValue: { baseUrl: 'https://jsonplaceholder.typicode.com' }}`;
    }

    if (importDevtools) {
      addModuleImportToRootModule(host, importDevtools, null as any, project);
    }

    if (importRouter) {
      addModuleImportToRootModule(host, importRouter, null as any, project);
    }

    if (provideEntityServiceConfig) {
      const modulePath = getAppModulePath(host, project.architect.build.options.main);
      const module = getModuleFile(host, modulePath);
      const providerChanges = addProviderToModule(module, modulePath, provideEntityServiceConfig, null);
      applyChanges(host, modulePath, providerChanges as InsertChange[]);
    }

    if (options.devtools) {
      context.logger.log('info', `🔥 AkitaNgDevtools is imported`);
    }

    if (options.withRouter || options.router) {
      context.logger.log('info', `🦄 AkitaNgRouterStoreModule is imported`);
    }

    if (options.httpEntityService) {
      context.logger.log('info', `🌈 NgEntityService is imported`);
    }

    return host;
  };
}

function log(): Rule {
  return (host: Tree, context: SchematicContext) => {
    context.logger.log('info', `👏 Create your first entity store by running: ng g af todos/todos`);

    return host;
  };
}

export function akitaNgAdd(options: Schema): Rule {
  return chain([
    options && options.skipPackageJson ? noop() : addPackageJsonDependencies(options),
    options && options.skipPackageJson ? noop() : installPackageJsonDependencies(),
    addModuleToImports(options),
    injectImports(options),
    setSchematicsAsDefault(),
    log()
  ]);
}
