'use strict';

const path = require('path');
const _ = require('lodash');

const tools = require('../../tools');
const configUtils = require('../../../csdr/config/config-utils');
const initUtils = require('../../../csdr/init/init-utils');

const subs = require('./routes-replacement-subs');

const { debug } = tools.getArgs();

/**
 *
 * ROUTES REPLACEMENT
 *
 */

/**
 * Process routes either for playground local environment OR by envTarget at build time,
 * injects the routesContent generated inside the project/src/app/app.routes.ts file from a base route file and
 * based on definitions in mywp-host-ui/assets/routes folder definitions as data input
 *
 * used by projects pre-build phase
 *
 * PUBLIC
 *
 * @param {project} project The CSDR project object fetched from csdr global projects config
 * @param {string} envTarget The build-time environment target provided to target specific routes for specific environment deployments
 * @param {boolean} build if the script is launched from a release pipeline OR from local CSDR playground environment (false in that case)
 * @returns {Promise} returns a Promise as it involves async I/O processes
 */
module.exports.buildRoutes = (project, envTarget, build = false) => {
  tools.logTitle('Starting routes replacement');

  // fetch custom config flag from project definition (used by host-playground-custom CSDR app config)
  const custom = project.externalRoutesSources.custom;

  // fetch generated envTarget
  const envTargetFinal = subs.getEnvTarget(envTarget, build);

  // proceed to routes individual fetching and replacements
  return Promise.resolve()
    .then(() => {
      // injecting routes json definitions into project assets
      injectRoutesConfig(project);

      // getting all routes defs to be processed
      const rawRoutesDefs = getRawRoutesDefs(project, envTargetFinal, custom);

      // getting project routes remapped
      const routes = getProjectRoutes(project, rawRoutesDefs);

      // generate Angular routes replacement
      generateAngularRoutes(project, routes);

      // generate feature libs files for lazy loaded playground routes
      generateLazyFeatureLibs(project, routes);

      // generate remotes-metadata files in Angular host
      generateRemotesMetadata(project, routes);

      if (project.externalRoutesSources && project.externalRoutesSources.generateLinks) {
        // generate links for sidebar menu entries
        generateLinks(project, routes, envTargetFinal);
      }
    })

    .then(() => {
      tools.logSuccess();
    })

    .catch((e) => {
      throw e;
    });
};







const injectRoutesConfig = (project) => {

  const npmPkg = project.externalRoutesSources.routesConfigNpmPkg;

  // check if package is locally cloned
  const localPackage = configUtils.packages.getPackages().filter((p) => {
    return p.npmPkg === npmPkg;
  })[0];

  let pkgAssetsPath;

  // if local package is found
  if (localPackage) {
    pkgAssetsPath = path.join(process.cwd(), 'packages', localPackage.name, 'assets');

    // if not sources are taken from the npm package def in node_modules
  } else {
    const npmPkgScope = npmPkg.substr(0, npmPkg.indexOf('/'));
    const npmPkgName = npmPkg.substr(npmPkg.indexOf('/') + 1);
    pkgAssetsPath = path.join(process.cwd(), 'node_modules', npmPkgScope, npmPkgName, 'assets');
  }

  if (!tools.isDirExists(pkgAssetsPath)) {
    tools.logWarning(`Linked config pkg : ${npmPkg} cannot be found in node_modules`);
    return;
  }

  const routesConfigPath = path.join(pkgAssetsPath, project.externalRoutesSources.routesConfigAssetsPath);

  tools.logInfo(`${routesConfigPath} - injecting in ${project.paths.assetsPath}`);
  tools.copydir(routesConfigPath, project.paths.assetsPath);

  if (project.externalRoutesSources.workspacesConfigAssetsPath) {
    const workspacesConfigPath = path.join(pkgAssetsPath, project.externalRoutesSources.workspacesConfigAssetsPath);
    tools.logInfo(`${workspacesConfigPath} - injecting in ${project.paths.assetsPath}`);
    tools.copydir(workspacesConfigPath, project.paths.assetsPath);
  }
};


const getCustomRoutesDefs = (project) => {
  let routes = [];

  tools.logInfo('Generating playground routes from custom config :');

  const customConfig = configUtils.global.getCustomConfig();

  if (Object.keys(customConfig).length === 0) {
    tools.logError('No custom config found in your local csdr setup, run "npm run init:custom" for generating your own');
    throw 'NO_CUSTOM_CONFIG_FOUND';
  }

  tools.logInfo('Custom config found :');
  console.log(customConfig);

  const routeDefsBaseFilename = 'route-defs-base.json';
  const routeDefsBaseJSON = require(path.join(project.paths.assetsPath, routeDefsBaseFilename));

  const getRouteFromNpmPkg = (npmPkg) => {
    let route = null;

    routeDefsBaseJSON.forEach((r) => {
      if (r.lazyLoadDef) {
        if (r.lazyLoadDef.npmPkg === npmPkg) {
          route = r;
        }
      }
    })

    return route;
  }

  // get routes from local packages to be installed
  customConfig.packages.forEach((p) => {
    const pkg = configUtils.packages.getPackage(p.name);

    tools.logInfo(`getting route def for : ${pkg.name} - ${pkg.npmPkg}`);
    const route = getRouteFromNpmPkg(pkg.npmPkg);

    if (route) {
      routes.push({ path: route.path, lazy: true });
    }
  });
  return routes;
}


const getRoutesDefs = (project, envTargetFinal) => {
  // getting generated from input params
  const euiVersion = configUtils.projects.getProjectEuiVersion(project);

  if (!euiVersion) {
    throw 'PROJECT_EUI_VERSION_NOT_FOUND';
  }

  const euiVersionNumber = euiVersion.split('.')[0];

  tools.logInfo(`Processing for envTarget : ${envTargetFinal}`);

  // getting the input file definitions of routes for replacements
  // from tokenized template in config -- allowing to control playground vs normal env (when using the MWP HOST for release)
  // format :
  //    playground : "routesFilenameTemplate": "route-defs-base-v@eui.version@-playground.json"
  //    host : "routesFilenameTemplate": "route-defs.@env.target@.json"

  let routeDefsInputFilename;

  if (!project.externalRoutesSources.routesFilenameTemplate) {
    throw 'ROUTES_CONFIG_V2_TEMPLATE_NOT_FOUND';
  }

  routeDefsInputFilename = project.externalRoutesSources.routesFilenameTemplate.replace('@eui.version@', euiVersionNumber);
  routeDefsInputFilename = routeDefsInputFilename.replace('@env.target@', envTargetFinal);

  tools.logInfo(`Starting replacement of routes based on generated template : ${routeDefsInputFilename}`);

  // Getting input routeDefs entry

  const routeDefsInputPath = path.join(project.paths.assetsPath, routeDefsInputFilename);

  if (!tools.isFileExists(routeDefsInputPath)) {
    tools.logError(`${routeDefsInputPath} File NOT FOUND`);
    throw 'ROUTE_DEFS_INPUT_FILE_NOT_FOUND';
  }

  const routeDefsInputJSON = require(path.join(routeDefsInputPath));

  return routeDefsInputJSON;
}




const getRawRoutesDefs = (project, envTargetFinal, custom) => {

  let routesDefsBase = [];

  if (custom) {
    routesDefsBase = getCustomRoutesDefs(project);

  } else {
    routesDefsBase = getRoutesDefs(project, envTargetFinal);
  }

  if (routesDefsBase.length === 0) {
    tools.logError('No routes base defs can be found, check your mywp-host-ui branch status or contact support');
    throw 'NO_ROUTES_DEFS'
  }


  // Merging content with "local" route file defs if existing

  let routes = [];

  if (envTargetFinal.indexOf('local') > -1) {
    const localRouteDefsInputFilename = `route-defs.${envTargetFinal}.json`;
    const localRouteDefsInputPath = path.join(project.paths.assetsPath, localRouteDefsInputFilename);

    if (tools.isFileExists(localRouteDefsInputPath)) {
      tools.logInfo(`Merging local configuration found : ${localRouteDefsInputFilename}`);
      const localRouteDefsInputJSON = require(path.join(localRouteDefsInputPath));

      routesDefsBase.forEach((route) => {
        const matchedRoute = localRouteDefsInputJSON.filter(newRoute => newRoute.path === route.path)[0];
        if (matchedRoute) {
          tools.logInfo(`-- replacing ${route.path} route entry with overriden local config`);
          tools.logInfo(`-----> ${JSON.stringify(matchedRoute)}`);
          routes.push(matchedRoute);
        } else {
          routes.push(route);
        }
      });
    }
  } else {
    routes = routesDefsBase;
  }

  return routes;
}







const getProjectRoutes = (project, routesDefs) => {
  let projectRoutes = [];

  const euiVersion = configUtils.projects.getProjectEuiVersion(project);

  const routeDefsBaseFilename = 'route-defs-base.json';
  const routeDefsBaseJSON = require(path.join(project.paths.assetsPath, routeDefsBaseFilename));

  routesDefs.forEach((route) => {
    tools.logInfo(`Processing route : `);
    console.log(route);

    // checking if current route being processed exists in the the base routes defs
    const defRoute = routeDefsBaseJSON.filter((r) => r.path === route.path)[0];

    if (!defRoute) {
      tools.logError('route not found for: ');
      console.log(route);
      throw 'ROUTE_NOT_FOUND';

    } else {
      let loadChildrenGenerated;
      let lazyLibDef = null;

      // in case of a lazy route, the route is generated from a import/export feature lib module TS file
      // those files are also generated based on the route "lazyDefs" data definition

      if (route.lazy) {
        let lazyLoadDef = null;

        if (Array.isArray(defRoute.lazyLoadDef)) {
          lazyLoadDef = defRoute.lazyLoadDef.filter(lld => lld.euiVersion === euiVersion)[0];
        } else {
          lazyLoadDef = defRoute.lazyLoadDef;
        }

        if (!lazyLoadDef) {
          console.log(`Unable to find lazyLoadDef route for path : ${defRoute.path}`);
          throw new Error('LAZY_ROUTE_DEF_NOT_FOUND');
        }

        const scopeName = lazyLoadDef.npmPkg.split('/')[0].substr(1);
        const pkgName = lazyLoadDef.npmPkg.split('/')[1];
        const lazyFeatureLibName = `${scopeName}-${pkgName}-${lazyLoadDef.moduleName}.lib.module`;

        if (euiVersion === '10.x') {
          loadChildrenGenerated = `loadChildren: "./features/${lazyFeatureLibName}#Module"`;
        } else {
          loadChildrenGenerated = `loadChildren: () => import("./features/${lazyFeatureLibName}").then(m => m.Module)`;
        }

        lazyLibDef = {
          filename: `${lazyFeatureLibName}.ts`,
          npmPkg: lazyLoadDef.npmPkg,
          moduleName: lazyLoadDef.moduleName,
        };

        // if the route is a remote, the elementLoader is then used as a default feature module loaded
      } else {
        if (euiVersion === '10.x') {
          loadChildrenGenerated = 'loadChildren: "./features/element-loader.module#ElementLoaderModule"';
        } else {
          loadChildrenGenerated = 'loadChildren: () => import("./features/element-loader.module").then(m => m.ElementLoaderModule)';
        }
      }

      // we define the route item definition, that will be injected in the project app.routes source
      const newAppRoute = {
        lazyLibDef: lazyLibDef,
        angularRouteDef: {
          path: defRoute.path,
          loadChildren: loadChildrenGenerated,
          data: {},
        },
        remote: false,
        remoteDef: null,
      };

      // in the case the route is auth guarded, we attach the definition of the auth parameters
      if (defRoute.authMetadata) {
        newAppRoute.angularRouteDef.data.id = defRoute.authMetadata.id;
        newAppRoute.angularRouteDef.data.ids = defRoute.authMetadata.ids;
        newAppRoute.angularRouteDef.canActivate = defRoute.authMetadata.canActivate;
      }

      // in the case there's a title for the route, we attach it
      if (defRoute.title) {
        newAppRoute.angularRouteDef.title = defRoute.title;
      }

      // in case of a remote, the "remoteDefs" data entry are processed
      // in case no remote corresponds to a remote path defined in the definitions, we throw an error and we stop the
      // script to avoid bad route data / or partial routes data generated
      if (route.remote) {
        let remoteDef;

        try {
          remoteDef = defRoute.remoteDefs.filter((r) => {
            return r.euiVersion === route.euiVersion;
          })[0];

        } catch {
          tools.logError('Remote route not found for: ');
          console.log(route);
          throw 'REMOTE_ROUTE_DEF_NOT_FOUND';
        }

        if (remoteDef) {
          newAppRoute.angularRouteDef.data.moduleId = remoteDef.moduleId;
          newAppRoute.angularRouteDef.data.elementTag = remoteDef.elementTag;
          newAppRoute.angularRouteDef.data.iframe = remoteDef.iframe;
          newAppRoute.remote = true;
          newAppRoute.remoteDef = remoteDef;

        } else {
          tools.logError('Remote route not found for: ');
          console.log(route);
          throw 'REMOTE_ROUTE_DEF_NOT_FOUND';
        }
      }

      projectRoutes.push(newAppRoute);
    }
  });

  return projectRoutes;
}








const generateAngularRoutes = (project, routes) => {
  tools.logInfo('Processing routes replacement');

  const replaceRoute = (route) => {
    let routeContent = '{\n';

    routeContent += `  path: '${route.path}',\n`;
    if (route.title) {
      routeContent += `  title: '${route.title}',\n`;
    }
    if (Object.keys(route.data).length > 0) {
      routeContent += '  data: {\n';

      if (route.data.id) {
        routeContent += `    id: '${route.data.id}',\n`;
      }
      if (route.data.ids) {
        routeContent += `    ids: ${route.data.ids},\n`;
      }
      if (route.data.featureName) {
        routeContent += `    featureName: '${route.data.featureName}',\n`;
      }
      if (route.data.moduleId) {
        routeContent += `    moduleId: '${route.data.moduleId}',\n`;
      }
      if (route.data.elementTag) {
        routeContent += `    elementTag: '${route.data.elementTag}',\n`;
      }
      if (route.data.iframe) {
        routeContent += `    iframe: ${route.data.iframe},\n`;
      }
      routeContent += '  },\n';
    }
    if (route.canActivate) {
      routeContent += `  canActivate: ${route.canActivate},\n`;
    }
    if (route.loadChildren) {
      routeContent += `  ${route.loadChildren},\n`;
    }
    routeContent += '},\n\n';

    return routeContent;
  };


  const euiVersion = configUtils.projects.getProjectEuiVersion(project);

  // getting routes file to replace (from /src/app host side)
  const routesFile = subs.getRoutesFile(project, euiVersion);
  if (!routesFile) {
    throw 'ROUTES_FILE_NOT_FOUND';
  }
  let routesFileContent = tools.getFileContent(routesFile);

  // replacing the placeholder content of the source routes file with the routes generated
  let placeHolderContent = '';

  routes.forEach((route) => {
    placeHolderContent += replaceRoute(route.angularRouteDef);
  });

  const placeHolderToken = '// routes-placeholder';
  routesFileContent = tools.replaceAll(routesFileContent, placeHolderToken, placeHolderContent);

  if (debug) {
    tools.logInfo('Routes file content replaced : ');
    console.log(routesFileContent);
  }

  // writing final routes content into project sources
  tools.writeFileContent(routesFile, routesFileContent);
}








const generateLazyFeatureLibs = (project, routes) => {
  tools.logInfo('Processing creation of features lib modules entries');

  const appFeatureLibs = routes.filter(r => r.lazyLibDef !== null);

  appFeatureLibs.forEach((r) => {
    const featureLibPath = path.join(process.cwd(), project.folder, 'src', 'app', 'features', r.lazyLibDef.filename);

    const featureLibDef = `
      import { NgModule } from '@angular/core';
      import { ${r.lazyLibDef.moduleName} as LibModule } from '${r.lazyLibDef.npmPkg}';

      @NgModule({
          imports: [LibModule],
          exports: [LibModule],
      })
      export class Module {
      }
    `;

    tools.logInfo(`Creating lazy feature lib module : ${r.lazyLibDef.filename}`);

    tools.writeFileContent(featureLibPath, featureLibDef);
  });
}









const generateLinks = (project, routes, envTargetFinal) => {
  tools.logInfo('Processing links generation');

  let routeDefsBaseLinksFileName;

  if (
    envTargetFinal === 'dev' ||
    envTargetFinal.indexOf('local') > -1
  ) {
    if (envTargetFinal.indexOf('dev-local') > -1) {
      routeDefsBaseLinksFileName = `route-defs-base-links-dev.json`
    } else if (envTargetFinal.indexOf('tst-local') > -1) {
      routeDefsBaseLinksFileName = `route-defs-base-links-test.json`
    } else if (envTargetFinal.indexOf('local-proxy') > -1) {
      routeDefsBaseLinksFileName = `route-defs-base-links-dev.json`
    } else {
      routeDefsBaseLinksFileName = `route-defs-base-links-${envTargetFinal}.json`
    }
  } else {
    routeDefsBaseLinksFileName = 'route-defs-base-links.json';
  }

  const routeDefsBaseLinksJSON = require(path.join(project.paths.assetsPath, routeDefsBaseLinksFileName));
  const routeDefsBaseFilename = 'route-defs-base.json';
  const routeDefsBaseJSON = require(path.join(project.paths.assetsPath, routeDefsBaseFilename));
  const workspaceDefsBaseFilename = 'workspace-defs-base.json';

  let workspaceDefsBaseJSON;
  if (tools.isFileExists(path.join(project.paths.assetsPath, workspaceDefsBaseFilename))) {
    workspaceDefsBaseJSON = require(path.join(project.paths.assetsPath, workspaceDefsBaseFilename));
  }

  const getRouteMenuDef = (link) => {
    let routeDef = null;

    if (link.url) {
      routeDefsBaseJSON.forEach((r) => {
        if (r.menuLinkDefs && r.menuLinkDefs.length > 0) {
          r.menuLinkDefs.forEach((md) => {
            if (md.url === link.url) {
              routeDef = md;
              routeDef.path = r.path;
              if (link.id) {
                routeDef.id = link.id;
              }
              if (link.optional) {
                routeDef.optional = link.optional;
              }
              if (link.workspace) {
                routeDef.workspace = link.workspace;
              }
              if (link.dashboardWidgetDisabled) {
                routeDef.dashboardWidgetDisabled = link.dashboardWidgetDisabled;
              }
              if (link.alwaysDisplayed) {
                routeDef.alwaysDisplayed = link.alwaysDisplayed;
              }

             return;
            }
          });
        }
      });
    }

    return routeDef;
  };

  // inner method for checking based on the "allowedEnvs" option provided on the links definition if the link has to be added
  // to the links list

  const isLinkAllowed = (link) => {
    // allow all link for playground - local env
    if (envTargetFinal.indexOf('local') > -1) {
      return true;
    }

    // checking if current envTarget generated is allowed
    if (link.allowedEnvs) {
      if (link.allowedEnvs.includes(envTargetFinal)) {
        return true;
      } else {
        return false;
      }
    } else {
      return true;
    }
  };

  // global links combining routes links and workspaces links
  let workspaceLinks = [];

  // Combining workspaces definitions with base links definitions

  if (workspaceDefsBaseJSON) {
    workspaceDefsBaseJSON.forEach((wp) => {
      if (wp.baseLinkDefs) {
        wp.baseLinkDefs.forEach((wpLink) => {
          workspaceLinks.push({
            ...wpLink,
            workspace: wp.id
          });
        });
      }
    });

    tools.logInfo(`Workspaces links generated : ${workspaceLinks.length}`);
  }

  const globalLinks = [
    ...routeDefsBaseLinksJSON,
    ...workspaceLinks,
  ];


  // processing each link and matching route from routes base data definitions

  const linksGenerated = [];

  globalLinks.forEach((link) => {
    let newLink = null;

    if (link.parentId) {
      if (isLinkAllowed(link)) {
        newLink = { ...link };
        newLink.children = [];

        link.children.forEach((subLink) => {
          if (isLinkAllowed(subLink)) {
            const defRoute = getRouteMenuDef(subLink);

            if (defRoute) {
              if (project.name.indexOf('playground') > 0) {
                const routePathInInputRoutes = routes.filter(r => r.angularRouteDef.path === defRoute.path)[0];

                if (!routePathInInputRoutes) {
                  defRoute.disabled = true;

                } else {
                  tools.logInfo(`[${defRoute.url}] --> route found in input routes - enabled`);
                }
              }
              newLink.children.push(defRoute);

            } else {
              newLink.children.push(subLink);
            }
          }
        });
      }
    } else {
      if (isLinkAllowed(link)) {
        const defRoute = getRouteMenuDef(link);

        if (defRoute) {
          if (project.name.indexOf('playground') > 0) {
            const routePathInInputRoutes = routes.filter(r => r.angularRouteDef.path === defRoute.path)[0];

            if (!routePathInInputRoutes && !link.static) {
              defRoute.disabled = true;
            } else {
              tools.logInfo(`[${defRoute.url}] --> route found in input routes - enabled`);
            }
          }
          newLink = {...defRoute};
        } else {
          newLink = {...link};
        }
      }
    }

    if (newLink) {
      linksGenerated.push(newLink);
    }
  });

  // if (debug) {
    // console.log('Links generated');
    // console.log(linksGenerated);
  // }

  tools.logInfo(`Links generated : ${linksGenerated.length} found`);

  tools.writeJsonFileSync(path.join(project.paths.assetsPath, 'route-defs-links.json'), linksGenerated);
}






const generateRemotesMetadata = (project, routes) => {
  const finalRemotesDefs = routes.filter(r => r.remote).map(r => r.remoteDef.moduleId);

  if (finalRemotesDefs.length > 0) {
    const finalRemotesDefsFile = path.join(project.paths.assetsPath, 'remotes-metadata.json');

    tools.logInfo(`Generating ${finalRemotesDefsFile}`);

    tools.writeJsonFileSync(finalRemotesDefsFile, finalRemotesDefs);
  }
}




module.exports.extractRemotesMetadata = (project) => {
  tools.logTitle('Extracting remotes-metadata.json for known participant remotes');

  const openidConfigFile = path.join(project.paths.assetsPath, 'openid-login-config.json');
  const finalRemotesDefsFile = path.join(project.paths.assetsPath, 'remotes-metadata.json');

  if (!tools.isFileExists(finalRemotesDefsFile)) {
    tools.logInfo('...remotes-metadata.json not found / not previously generated ... skipping');
    return;
  }

  return Promise.resolve()
    .then(() => {
      if (!project.standalone) {
        return initUtils.remotes.cloneRemotesConfig();
      }
    })

    .then(() => {
      let remoteMetadataJSON = tools.getJsonFileContent(finalRemotesDefsFile);
      const openidConfigJSON = tools.getJsonFileContent(openidConfigFile);

      remoteMetadataJSON.forEach((r) => {
        tools.logInfo(`Fetching remote info for : ${r}`);

        let remote;
        if (configUtils.remotes.isVirtualRemote(r)) {
          remote = configUtils.remotes.getRemote(r);
        } else {
          remote = configUtils.packages.getPackage(r, true, true);
        }

        if (remote && remote.build && remote.build.hasParticipants) {
          let participantsList;
          if (remote.euiVersion === '15.x') {
            participantsList = openidConfigJSON.modules[remote.build.configModuleName].participants_v15
          } else {
            participantsList = openidConfigJSON.modules[remote.build.configModuleName].participants
          }
          if (participantsList) {
            let participantRemotes = [];
            Object.keys(participantsList).forEach((p) => {
              participantRemotes.push(participantsList[p].moduleId);
            });
            tools.logInfo('Found participant remotes : ');
            console.log(participantRemotes);

            remoteMetadataJSON = [...remoteMetadataJSON, ...participantRemotes];
          }
        }
      })

      remoteMetadataJSON = tools.removeArrayDuplicates(remoteMetadataJSON);

      tools.logInfo('Final remotes metadata list found :');
      console.log(remoteMetadataJSON);

      return tools.writeJsonFileSync(finalRemotesDefsFile, remoteMetadataJSON);
  })

  .catch((e) => {
    throw e;
  });
}
