/**
 * TestIt API
 */
import {
  CaseInfo,
  IBuild,
  IGetSuggestTestCasesResponseDTO,
  IProject,
  IProjectInfo,
  IProjectKeyword,
  ISectionInfo,
  ISuite,
  ITestCase,
  ITestExecution,
  ITestPlan,
  IVersion,
  stepsInfo
} from "./dto";
import { IGlobalData } from "./dto";
import _, { identity } from "lodash";
import qs, { stringify } from "qs";
import * as Parser from "../parser/parser";
import HttpClient from "../util/httpClient";
import {
  PREDEFINED_PROPERTY_MAP,
  PREDEFINED_VALUE_MAP,
  OPERATOR_MAP,
  Scope,
  CacheKey,
  KeywordsMap
} from "./constants";
import { SearchOptions, TemplateInfo } from "./dto";
import { PASSWORD, USERNAME } from "../fixtures/fixtures";
const log4js = require("log4js");
const logger = log4js.getLogger();

export default class TestItSDK {
  private endpoint: string;
  private userName: string | null;
  private accessToken: string | null;
  private cache: Map<string, any>;
  private httpClient: HttpClient;
  constructor(endpoint: string) {
    this.endpoint = endpoint;
    this.userName = null;
    this.accessToken = null;
    this.cache = new Map<string, any>();
    this.httpClient = new HttpClient();
  }

  /**
   * Return true if client is authorized.
   */
  public getAutorized(): boolean {
    return this.accessToken != null;
  }
  /**
   * Login to TestIt. Save cookies if login successfully.
   * @param username
   * @param password
   */
  public async login(
    username: string | undefined,
    password: string | undefined
  ): Promise<void> {
    try {
      const url = `${this.endpoint}/api/v1/login`;
      const data = { username, password };
      const headers = {
        "Content-Type": "application/json"
      };
      const config = { headers, data };
      const response = await this.httpClient.post(url, config);
      this.userName = response.data.username;
      this.accessToken = response.data.access_token;
      logger.debug(response.data);
      await this.init();
    } catch (err) {
      throw new Error(
        "Failed to login TestIt, please check your username and password."
      );
    }
    logger.debug("----Welcome! Login Successfully!-----");
  }

  /**
   * Logout Testit, delete cookies.
   */
  public logout(): void {
    this.userName = null;
    this.accessToken = null;
  }

  /**
   * Initialize sdk,fetch global data.
   * @return {Promise<void>}
   */
  public async init(): Promise<void> {
    const globalData = await this.getGlobalData();
    this.cache.set(CacheKey.GLOBAL_DATA, globalData);
  }

  /**
   * Fetch fields information by project id for further using.
   * @param projectId
   */
  public async fetchFieldsInfoById(projectId: number): Promise<any> {
    const url = `${this.endpoint}/api/v1/fields?projectId=${projectId}`;
    const headers = {
      Authorization: this.accessToken,
      "Content-type": "application/json"
    };
    const config = {
      headers
    };
    const response = await this.httpClient.get(url, config);
    return response.data;
  }
  /**
   * Get search elements map from fields by name.
   * @param response
   * @param name (like Keywords Subtype...)
   */
  private async getFieldsInfoMapByName(
    response: any,
    name: string
  ): Promise<Map<string, number>> {
    var fieldsIdxMap = await this.getFieldsIndexMap(response);
    var index = fieldsIdxMap.get(name);
    if (index == undefined) {
      throw new Error(`Not found the name : ${name}`);
    }
    const values = response[index].values;
    var resMap = new Map<string, number>();
    for (var i = 0; i < values.length; i++) {
      resMap.set(values[i].name, values[i].id);
    }
    return resMap;
  }
  /**
   * Get field's response values map (name -> index).
   * @param response
   */
  private async getFieldsIndexMap(response: any): Promise<Map<string, number>> {
    var valuesMap = new Map<string, number>();
    for (var i = 0; i < response.length; i++) {
      valuesMap.set(response[i].name, i);
    }
    return valuesMap;
  }
  /**
   * Get field's response values map (name -> id).
   * @param response
   */
  private async getFieldsIdMap(response: any): Promise<Map<string, number>> {
    var valuesMap = new Map<string, number>();
    for (var i = 0; i < response.length; i++) {
      valuesMap.set(response[i].name, response[i].id);
    }
    return valuesMap;
  }
  /**
   * Get projects.
   * @return {Promise<IProject[]>}
   */
  public async getProjects(): Promise<IProject[]> {
    const cacheKey = CacheKey.PROJECTS;
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey);
    }
    const url = `${this.endpoint}/api/v1/projects`;
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    this.cache.set(cacheKey, response.data);
    return response.data;
  }
  /**
   * Generate prefix project map.
   */
  private async genPrefixProjectMap(): Promise<Map<number, string>> {
    const response = await this.getProjects();
    var prefixMap = new Map<number, string>();
    for (var i = 0; i < response.length; i++) {
      prefixMap.set(response[i].id, response[i].prefix);
    }
    return prefixMap;
  }
  /**
   * Get project by name.
   * @param projectName
   */
  public async getProjectByName(
    projectName: string
  ): Promise<IProject | undefined> {
    const totalProjects = await this.getProjects();
    return _.find(totalProjects, p => p.name === projectName);
  }
  /**
   * Get project id by name.
   * @param projectName
   */
  private async getProjectIdByName(projectName: string): Promise<any> {
    const project = await this.getProjectByName(projectName);
    if (!project) {
      throw new Error(`Project not found : ${projectName}.`);
    }
    return project["id"];
  }
  /**
   * Get project by project id.
   * @param projectId
   */
  public async getProjectById(projectId: number): Promise<IProjectInfo> {
    const cacheKey = `${CacheKey.PROJECT} : ${projectId}`;
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey);
    }
    const url = `${this.endpoint}/api/v1/project/${projectId}`;
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    //update : response total data of project.
    this.cache.set(cacheKey, response.data);
    return response.data;
  }

  /**
   * Get keywords by project id.
   * 【Error】Project doesn't include keywords!Not really go.
   * @param projectId
   */
  public async getKeywordsByProjectId(
    projectId: number
  ): Promise<IProjectKeyword[]> {
    const cacheKey = `${CacheKey.KEYWORDS} : ${projectId}`;
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey);
    }
    const url = `${this.endpoint}/api/v1/project/${projectId}`;
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    this.cache.set(cacheKey, response.data);
    return response.data;
  }
  /**
   * Get project tree by project id.
   * @param projectId
   */
  public async getProjectTreeById(projectId: number): Promise<ISuite[]> {
    const cacheKey = `${CacheKey.PROJECT_TREE} : ${projectId}`;
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey);
    }
    const url = `${this.endpoint}/api/v1/project/${projectId}/tree`;
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    this.cache.set(cacheKey, response.data);
    return response.data;
  }
  /**
   * Get section by section id.
   * @param sectionId
   */
  public async getSectionById(sectionId: number): Promise<ISectionInfo> {
    const cacheKey = `${CacheKey.SECTION} : ${sectionId}`;
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey);
    }
    const url = `${this.endpoint}/api/v1/section/${sectionId}`;
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    this.cache.set(cacheKey, response.data);
    return response.data;
  }

  /**
   * Get test case by test case id. - 17
   * @param testCaseId
   */
  public async getTestCaseById(testCaseId: number): Promise<ITestCase> {
    try {
      const url = `${this.endpoint}/api/v1/case/${testCaseId}`;
      const headers = {
        Authorization: this.accessToken,
        "Content-Type": "application/json"
      };
      const config = { headers };
      const response = await this.httpClient.get(url, config);
      return response.data;
    } catch (err) {
      throw new Error("404 Not Found.Please make sure the right testCaseId");
    }
  }

  /**
   * Get many test cases by test case ids.
   * @param testCaseIds
   */
  public async getTestCasesByIds(testCaseIds: number[]): Promise<ITestCase[]> {
    var prefix = `${this.endpoint}/api/v1/cases?`;
    testCaseIds.forEach(function (value) {
      prefix = `${prefix}id=${value}&`;
    });
    const url = prefix.substring(0, prefix.length - 1);
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    return response.data;
  }
  /**
   * Get test plans from test case by test case id.
   * @param testCaseId
   */
  public async getTestPlansByTestCaseId(
    testCaseId: number
  ): Promise<ITestPlan[]> {
    const cacheKey = `${CacheKey.TEST_PLAN} : ${testCaseId}`;
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey);
    }
    const url = `${this.endpoint}/api/v1/case/${testCaseId}/testplans`;
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    this.cache.set(cacheKey, response.data);
    return response.data;
  }
  /**
   * Get test plans by project ids.
   * @param projectIds
   */
  public async getTestPlansByProjectIds(
    projectIds: number[]
  ): Promise<ITestPlan[]> {
    var prefix = `${this.endpoint}/api/v1/testplans?`;
    projectIds.forEach(function (value) {
      prefix = `${prefix}projectId=${value}&`;
    });
    const url = prefix.substring(0, prefix.length - 1);
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    return response.data;
  }
  /**
   * Get execution-history by test case id.
   * @param testCaseId
   */
  public async getExecutionHistoryByTestCaseId(
    testCaseId: number
  ): Promise<ITestExecution> {
    const url = `${this.endpoint}/api/v1/case/${testCaseId}/execution-history`;
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    return response.data;
  }
  /**
   * Get test case version by version id.
   * @param versionId
   */
  public async getCaseVersionByVersionId(versionId: number): Promise<IVersion> {
    const url = `${this.endpoint}/api/v1/case/version/${versionId}`;
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    return response.data;
  }

  /**
   * Get test case by key.
   * 【Example】key = JPT-3166
   * @param key
   */
  public async getTestCaseByKey(key: string): Promise<ITestCase> {
    const url = `${this.endpoint}/api/v1/case/${key}`;
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "applicaiton/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    return response.data;
  }
  /**
   * Get builds by test plan id.
   * @param testPlanId
   */
  public async getBuildsByTestPlanId(testPlanId: number): Promise<IBuild[]> {
    const url = `${this.endpoint}/api/v1/testplan/${testPlanId}/builds`;
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    return response.data;
  }
  /**
   * Get test plan by test plan id.
   * @param testPlanId
   */
  public async getTestPlanById(testPlanId: number): Promise<ITestPlan> {
    const url = `${this.endpoint}/api/v1/testplan/${testPlanId}`;
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    return response.data;
  }

  /**
   * Get tree by test plan id.
   * @param testPlanId
   */
  public async getTreeByTestPlanId(testPlanId: number): Promise<ITestPlan> {
    const url = `${this.endpoint}/api/v1/testplan/${testPlanId}/tree`;
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    return response.data;
  }
  /**
   * Get execution progress by test plan id.
   * @param testPlanId
   */
  public async getExecutionProgressByTestPlanId(
    testPlanId: number
  ): Promise<ITestExecution> {
    const url = `${this.endpoint}/api/v1/testplan/${testPlanId}/execution-progress`;
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    return response.data;
  }
  /**
   * Get build by build id.
   * @param buildId
   */
  public async getBuildById(buildId: number): Promise<IBuild> {
    const url = `${this.endpoint}/api/v1/build/${buildId}`;
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    return response.data;
  }
  /**
   * Get suite by suite id.
   * @param suiteId
   */
  public async getSuiteById(suiteId: number): Promise<ISuite> {
    const cacheKey = `${CacheKey.SUITES}:${suiteId}`;
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey);
    }
    const url = `${this.endpoint}/api/v1/suite/${suiteId}`;
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    this.cache.set(cacheKey, response.data);
    return response.data;
  }

  /**
   * Get suite tree by suite id.
   * @param suiteId
   */
  public async getSuiteTreeById(suiteId: number): Promise<ISuite[]> {
    const cacheKey = `${CacheKey.SUITE_TREE}:${suiteId}`;
    if (this.cache.has(cacheKey)) {
      return this.cache.get(cacheKey);
    }
    const url = `${this.endpoint}/api/v1/suite/${suiteId}/tree`;
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    this.cache.set(cacheKey, response.data);
    return response.data;
  }

  /* Search API use in cases */

  /**
   * Search test cases by json query.
   * @param query
   * @param scope
   */
  private async searchTestCasesByJsonQuery(
    query: any,
    scope: Scope = "TEST_SUITES"
  ): Promise<any> {
    const criterionJson = query; /* JSON.stringify(query); */
    const url = `${this.endpoint}/testCases/search`;
    const data = {
      type: "search",
      scope,
      criterionJson
    };
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8"
    };
    const config = {
      headers,
      data: qs.stringify(data)
    };
    const response = await this.httpClient.post(url, config);
    return response.data;
  }

  /**
   * Flatten response data.
   * @param root
   */
  private flat(root: any) {
    const cases: any[] = [];
    const fifo: any[] = [root];
    while (fifo.length > 0) {
      const node = fifo.shift();
      if ("case" !== node.type) {
        if (node.children && node.children.length > 0) {
          node.children.forEach((child: { path: any[] }) => {
            if (!child.path) {
              child.path = [];
            }
            child.path.push(node.name);
          });
          fifo.push(...node.children);
        }
      } else {
        cases.push(node);
      }
    }
    return cases;
  }
  /**
   * Search and generate template info by search Option.
   * @param searchOption
   */
  public async generateTemplateInfoByOptions(
    searchOption: SearchOptions
  ): Promise<any> {
    const query = await this.generateQuery(searchOption);
    const response = await this.searchTestCasesByQuery(
      query,
      "TEST_SUITES",
      searchOption.suiteName
    );
    const flatResponse = this.flat(response);
    const flatLength = flatResponse.length;
    const resultData = [];
    for (var i = 0; i < flatLength; i++) {
      var tempKey = `${flatResponse[i].prefix}-${flatResponse[i].externalId}`;
      resultData.push(await this.generateTemplatesInfoByKey(tempKey));
    }
    return resultData;
  }
  /**
   * Store template file by search options.
   * @param searchOption
   * @param path
   */
  public async storeTemplateFileByOptions(
    searchOption: SearchOptions,
    path: string
  ): Promise<any> {
    const query = await this.generateQuery(searchOption);
    const response = await this.searchTestCasesByQuery(
      query,
      "TEST_SUITES",
      searchOption.suiteName
    );
    const flatResponse = this.flat(response);
    const flatLength = flatResponse.length;
    for (var i = 0; i < flatLength; i++) {
      var tempKey = `${flatResponse[i].prefix}-${flatResponse[i].externalId}`;
      var fileName = `${path}${tempKey}.test.ts`;
      var info = await this.generateTemplatesInfoByKey(tempKey);
      this.writeTemplateInfo(info, fileName);
    }
    logger.debug("Store Successfully");
  }
  /**
   * Store files in tree by search options.
   * [Abandon]Please replace with the API of [storeFilesBySearchOptions]
   * @param searchOption
   * @param path
   */
  private async storeFilesInTreeByOptions(
    searchOption: SearchOptions,
    path: string,
    templatePath?: string
  ): Promise<any> {
    const query = await this.generateQuery(searchOption);
    //flatten response
    //suite name is used to filter the search result
    const response = await this.searchTestCasesByQuery(
      query,
      "TEST_SUITES",
      searchOption.suiteName
    );
    //read template file
    const fs = require("fs");
    // if(!(templatePath === undefined)){
    //     var templateInfo = fs.readFileSync(templatePath,'utf8');
    // }
    //by suite name
    if (response.length > 1) {
      for (var i = 0; i < response.length; i++) {
        const flatResponse = this.flat(response[i]);
        const flatLength = flatResponse.length;
        for (var j = 0; j < flatLength; j++) {
          var tempKey = `${flatResponse[j].prefix}-${flatResponse[j].externalId}`;
          await this.storeFileInTreeByCaseKey(tempKey, path, templatePath);
        }
        logger.debug(`Total Store ${flatLength} files`);
        logger.debug("Store Successfully");
      }
    } else {
      //not by suite name
      const flatResponse = this.flat(response[0]);
      const flatLength = flatResponse.length;
      for (var i = 0; i < flatLength; i++) {
        var tempKey = `${flatResponse[i].prefix}-${flatResponse[i].externalId}`;
        await this.storeFileInTreeByCaseKey(tempKey, path, templatePath);
      }

      logger.debug(`Total Store ${flatLength} files`);
      logger.debug("Store Successfully");
    }
  }

  /**
   * Store files by search option(use template render engine)
   * @param searchOption
   * @param path
   * @param templatePath
   */
  public async storeFilesBySearchOptions(
    searchOption: SearchOptions,
    path: string,
    templatePath?: string
  ): Promise<any> {
    //read template file （only read one time）
    const fs = require("fs");
    //make sure template path is true
    var templateInfo: string;
    if (templatePath === undefined) {
      templateInfo = await fs.readFileSync(
        "../templates/Default.txt",
        "UTF-8",
        function (err: any) {
          throw new Error("Can't not find default.txt");
        }
      );
    } else {
      templateInfo = await fs.readFileSync(templatePath, "UTF-8", function (
        err: any
      ) {
        throw new Error(`Can't not find ${templatePath}`);
      });
    }
    //if contains case id,search then return
    if (!(searchOption.caseId === undefined)) {
      const response = await this.getTestCaseByKey(searchOption.caseId);
      const singleCaseInfo: CaseInfo = await this.fillCaseInfoMap(
        searchOption.caseId,
        response
      );
      await this.storeFileInTree(singleCaseInfo, path, templateInfo);
      logger.debug(`Store ${searchOption.caseId} Successfully`);
      return;
    }
    //generate query by search option(except suite name)
    const query = await this.generateQuery(searchOption);
    //suite name is used to filter the search result
    const response = await this.searchTestCasesByQuery(
      query,
      "TEST_SUITES",
      searchOption.suiteName
    );
    if (response.length == 0 || response == null)
      throw new Error("Search for nothing");
    //handle response and get all cases info
    const cases: CaseInfo[] = await this.handleResponse(response);
    const fileGenerateTask = [];
    for (const item of cases) {
      fileGenerateTask.push(
        new Promise<void>(async resolve => {
          await this.storeFileInTree(item, path, templateInfo);
          resolve();
        })
      );
    }
    //Promise all storage
    await Promise.all(fileGenerateTask);
    logger.debug(
      `[Store Finished] Total Store ${fileGenerateTask.length} files`
    );
  }
  /**
   * Handle search response to generate case info map
   * @param response
   */
  async handleResponse(response: any): Promise<CaseInfo[]> {
    var caseInfoMap: CaseInfo[] = [];
    const caseInfoSyncTask = [];
    if (response.length > 1) {
      for (var i = 0; i < response.length; i++) {
        const flatResponse = this.flat(response[i]);
        //if repeat suite name,clear case info map
        for (const item of flatResponse) {
          caseInfoSyncTask.push(
            new Promise<void>(async resolve => {
              var tempKey = `${item.prefix}-${item.externalId}`;
              const caseResponse = await this.getTestCaseByKey(tempKey);
              const caseInfo = await this.fillCaseInfoMap(
                tempKey,
                caseResponse
              );
              caseInfoMap.push(caseInfo);
              resolve();
            })
          );
          // logger.debug(`${caseInfoMap[0].ascendants} ----- Search result : ${flatResponse.length}`)
          logger.debug(`Ready to store ${caseInfoMap.length} files`);
        }
      }
      //get all caseInfo
      await Promise.all(caseInfoSyncTask);
      logger.debug(`Total Search : ${response.length} suite name tree`);
    } else {
      //not by suite name
      const flatResponse = this.flat(response[0]);
      const caseInfoSyncTask = [];
      for (const item of flatResponse) {
        caseInfoSyncTask.push(
          new Promise<void>(async resolve => {
            var tempKey = `${item.prefix}-${item.externalId}`;
            const caseResponse = await this.getTestCaseByKey(tempKey);
            const caseInfo = await this.fillCaseInfoMap(tempKey, caseResponse);
            caseInfoMap.push(caseInfo);
            resolve();
          })
        );
      }
      await Promise.all(caseInfoSyncTask);
      logger.debug(`Ready to Store ${flatResponse.length} files`);
    }
    return caseInfoMap;
  }
  /**
   * Fill case info map
   * @param tempKey
   * @param caseResponse
   */
  private async fillCaseInfoMap(
    tempKey: string,
    caseResponse: any
  ): Promise<CaseInfo> {
    var caseInfo = {
      id: tempKey,
      name: caseResponse.name,
      suite: await this.fetchTemplateFieldInfo(caseResponse, "suite"),
      priority: `P${caseResponse.priority}`,
      ids: [tempKey],
      ascendants: await this.fetchTemplateFieldInfo(caseResponse, "ascendants"),
      keywords: await this.fetchTemplateFieldInfo(caseResponse, "keywords"),
      maintainers: await this.fetchTemplateFieldInfo(
        caseResponse,
        "maintainers"
      ),
      docs: await this.fetchTemplateFieldInfo(caseResponse, "docs"),
      testSteps: await this.fetchTemplateFieldInfo(caseResponse, "testSteps"),
      preconditions: await this.fetchTemplateFieldInfo(
        caseResponse,
        "preconditions"
      ),
      accountType: await this.fetchTemplateFieldInfo(
        caseResponse,
        "accountType"
      ),
      extensionType: await this.fetchTemplateFieldInfo(
        caseResponse,
        "extensionType"
      ),
      entryPoint: await this.fetchTemplateFieldInfo(caseResponse, "entryPoint")
    };
    return caseInfo;
  }
  /**
   * Search test cases by query.
   * 【Query Example】const query = `version eq "active" and project_id in "[1311]" and field.5371560 in "[6234174]" and field.224 in "[11949947]"`;
   * @param query
   * @param scope
   * @param suiteName use suite name to filter
   */
  async searchTestCasesByQuery(
    query: string,
    scope: Scope = "TEST_SUITES",
    suiteName?: string
  ): Promise<any> {
    const queryJson = await this.generateSearchJson(query);
    const response = await this.searchTestCasesByJsonQuery(queryJson, scope);
    //handle the response by suite name
    if (!(suiteName === undefined)) {
      const filterResult = await this.SearchSuiteBlkInResponse(
        response,
        suiteName
      );
      return filterResult;
    } else {
      //not by suite name
      const result = [];
      result.push(response);
      return result;
    }
  }

  /**
   * Search suite by suite name in response
   * @param response
   * @param suiteName
   */
  private async SearchSuiteBlkInResponse(
    response: any,
    suiteName: string
  ): Promise<any> {
    var suiteQueue = [];
    var resultData = [];
    // search from project
    for (var i = 0; i < response.children.length; i++) {
      var tempProject = response.children[i];
      // handle temp project
      if (!(tempProject.children === undefined)) {
        // search suite by suite name(dfs)
        for (var j = 0; j < tempProject.children.length; j++) {
          suiteQueue.push(tempProject.children[j]);
          while (suiteQueue.length != 0 && suiteQueue != null) {
            var tempSuite: any = await suiteQueue.shift();
            if (tempSuite === undefined) continue;
            if (tempSuite.name == suiteName) {
              resultData.push(tempSuite);
            }
            //search for suite children
            if (
              !(tempSuite.children === undefined) &&
              tempSuite.type == "suite"
            ) {
              for (var k = 0; k < tempSuite.children.length; k++) {
                if (tempSuite.children[k].type == "suite") {
                  suiteQueue.push(tempSuite.children[k]);
                }
              }
            }
          }
        }
      }
    }
    return resultData;
  }
  /**
   * Convert search query to json.
   * @param query
   */
  private async generateSearchJson(query: string): Promise<any> {
    const queryParse = Parser.parse(query);
    // logger.debug(queryParse);
    var queryJson = await JSON.stringify(queryParse);
    //Handle the queryJson.
    const s1 = '\\"';
    queryJson = queryJson.replace(s1, "").replace(s1, "");
    queryJson = queryJson.replace(/\"\\\"/g, "").replace(/\\\"\"/g, "");
    //filter search condition of suiteName value
    queryJson = queryJson.replace(/'/g, '"');
    // logger.debug(queryJson);
    return queryJson;
  }
  /**
   * Search testCases by search options(projectName,keywords,subtype).
   * @param projectName
   * @param keywords
   * @param subtype
   */
  public async searchCasesBySearchOptions(
    searchOption: SearchOptions
  ): Promise<any> {
    //if search options constain search id
    if (!(searchOption.caseId === undefined)) {
      const response = await this.getTestCaseByKey(searchOption.caseId);
      await this.printSearchInfo(response);
      return;
    }
    //ToQuery
    const query = await this.generateQuery(searchOption);
    const searchRes = await this.searchTestCasesByQuery(
      query,
      "TEST_SUITES",
      searchOption.suiteName
    );
    //count response lenth
    //by suite name
    if (searchRes.length > 1) {
      const flatResponseArr = [];
      logger.debug(
        `->> Find ${searchRes.length} suites according to the ${searchOption.suiteName}`
      );
      for (var i = 0; i < searchRes.length; i++) {
        const flatResponse = this.flat(searchRes[i]);
        logger.debug(`Search Result: `);
        this.printSearchInfo(flatResponse);
        flatResponseArr.push(flatResponse);
        logger.debug(`Total Search : ${flatResponse.length}\n`);
      }
      return flatResponseArr; //return array
    } else {
      //not by suite name
      logger.debug(
        `->> Find 1 suite according to the ${searchOption.suiteName}`
      );
      const flatResponse = this.flat(searchRes[0]);
      logger.debug(`Search Result: `);
      this.printSearchInfo(flatResponse);
      logger.debug(`Total Search : ${flatResponse.length}`);
      return flatResponse; // return response
    }
  }
  /**
   * Print search result info
   * @param flatResponse
   */
  private async printSearchInfo(flatResponse: any): Promise<any> {
    for (var i = 0; i < flatResponse.length; i++) {
      const temp = flatResponse[i];
      var info = `\t${temp.prefix}-${temp.externalId}:${temp.name}`;
      logger.debug(info);
    }
  }
  /**
   * Get suite id by name.
   * @param suiteName
   * @param projectId
   */
  private async getSuiteIdByName(
    suiteName: string,
    projectId: number
  ): Promise<number[]> {
    const projectTree = await this.getProjectTreeById(projectId);
    // get suite id by suite name(bfs)
    var searchQueue: any = [];
    var suiteId: number[] = [];
    for (var i = 0; i < projectTree.length; i++) {
      await searchQueue.push(projectTree[i]);
      // logger.debug(i);
      // traverse first level
      while (searchQueue.length != 0 && searchQueue != null) {
        var tempSuite = await searchQueue.shift();
        if (tempSuite === undefined) continue;
        try {
          if (tempSuite.name == suiteName) {
            //store suiteId
            suiteId.push(tempSuite.id);
          }
        } catch (err) {
          logger.debug(tempSuite);
        }
        // has children
        if (!(tempSuite.children === undefined) && tempSuite.type == "suite") {
          for (var j = 0; j < tempSuite.children.length; j++) {
            if (tempSuite.children[j].type == "suite") {
              await searchQueue.push(tempSuite.children[j]);
            }
          }
        }
      }
    }
    return suiteId;
  }
  /**
   * Search cases by suite name.
   * [Abandon]
   * @param suiteName
   * @param projectName
   */
  private async searchCasesBySuiteName(
    suiteName: string,
    projectName: string
  ): Promise<any> {
    const projectId = await this.getProjectIdByName(projectName);
    const caseKeyMap = [];
    // get project prefix map
    const prefixMap = await this.genPrefixProjectMap();
    const prefix = await prefixMap.get(projectId);
    // get suite id by suite name(bfs)
    const suiteId = await this.getSuiteIdByName(suiteName, projectId);
    logger.debug(suiteId);
    // get suite tree by id and flat it get total case
    logger.debug(`Search for ${suiteId.length} suite`);
    for (var k = 0; k < suiteId.length; k++) {
      // get suite tree and suite
      const suiteTree = await this.getSuiteTreeById(suiteId[k]);
      const suite = await this.getSuiteById(suiteId[k]);
      // get the store path by suite
      var path = "";
      for (var i = 0; i < suite.ascendants.length; i++) {
        path = `${path}${suite.ascendants[i].name}/`;
      }
      logger.debug(`The path : ${path}`);
      // handle the flatten response
      for (var i = 0; i < suiteTree.length; i++) {
        var flatResponse = await this.flat(suiteTree[i]);
        for (var j = 0; j < flatResponse.length; j++) {
          var caseKeyInfo = `${prefix}-${flatResponse[j].externalId}`;
          logger.debug(caseKeyInfo);
          caseKeyMap.push(caseKeyInfo);
        }
      }
    }
    logger.debug(`Total Search Result : ${caseKeyMap.length}`);
    return caseKeyMap;
  }

  /**
   * Store cases by suite name.
   * [Abandon]
   * @param suiteName
   * @param projectName
   * @param path
   */
  private async storeCasesBySuiteName(
    suiteName: string,
    projectName: string,
    path: string,
    templatePath?: string
  ): Promise<any> {
    const caseKeyMap = await this.searchCasesBySuiteName(
      suiteName,
      projectName
    );
    for (var i = 0; i < caseKeyMap.length; i++) {
      await this.storeFileInTreeByCaseKey(caseKeyMap[i], path, templatePath);
    }
  }
  /**
   * Generate search query by project name、keywords and suite.
   * @param projectName
   * @param keywords
   * @param subtype
   */
  async generateQuery(searchOption: SearchOptions): Promise<string> {
    //get option params
    const projectName = searchOption.projectName;
    const projectId = await this.getProjectIdByName(projectName);
    //Fetch fields info
    const response = await this.fetchFieldsInfoById(projectId);
    //Generate map
    const keyworsMap = await this.getFieldsInfoMapByName(response, "Keywords");
    const subtypeMap = await this.getFieldsInfoMapByName(response, "Subtype");
    const automatedByMap = await this.getFieldsInfoMapByName(
      response,
      "Automated By"
    );
    const fieldsIdMap = await this.getFieldsIdMap(response);
    // Object.keys(searchOption).forEach((key)=>{
    //   console.log(searchOption.key);
    //   console.log(searchOption[key]);
    // });

    //For key Map
    var keyWordQuery = "";
    if (!(searchOption.keywords === undefined)) {
      const keywords = searchOption.keywords;
      var keywordsArr = [];
      for (var i = 0; i < keywords.length; i++) {
        keywordsArr.push(keyworsMap.get(keywords[i]));
      }
      keyWordQuery = `${keyWordQuery} and field.${fieldsIdMap.get(
        "Keywords"
      )} in "[${keywordsArr}]"`;
    }
    //For Suite Map
    var subtypeQuery = "";
    if (!(searchOption.subtype === undefined)) {
      const subtype = searchOption.subtype;
      var subtypeArr = [];
      for (var i = 0; i < subtype.length; i++) {
        subtypeArr.push(subtypeMap.get(subtype[i]));
      }
      subtypeQuery = `${subtypeQuery} and field.${fieldsIdMap.get(
        "Subtype"
      )} in "[${subtypeArr}]"`;
    }
    //Handle suite query
    // var suiteQuery = "";
    // if (!(searchOption.suiteName === undefined)) {
    //     const suiteName = searchOption.suiteName;
    //     suiteQuery = `${suiteQuery} and suite_name like "'${suiteName}'"`;
    // }
    //Handle priority query
    var priorityQuery = "";
    if (!(searchOption.priority === undefined)) {
      const priority = searchOption.priority;
      priorityQuery = `${priorityQuery} and priority in "[${searchOption.priority}]"`;
    }
    // handle case name query
    var caseNameQuery = "";
    if (!(searchOption.caseName === undefined)) {
      const caseName = searchOption.caseName;
      caseNameQuery = `${caseNameQuery} and case_name like "'${caseName}'"`;
    }
    // handle automatedBy by query
    var automatedByQuery = "";
    if (!(searchOption.automatedBy === undefined)) {
      const automatedBy = searchOption.automatedBy;
      var automatedByArr = [];
      for (var i = 0; i < automatedBy.length; i++) {
        automatedByArr.push(automatedByMap.get(automatedBy[i]));
      }
      automatedByQuery = `${automatedByQuery} and field.${fieldsIdMap.get(
        "Automated By"
      )} in "[${automatedByArr}]"`;
    }
    // handle date created
    var dateCreatedQuery = "";
    if (!(searchOption.dateCreated === undefined)) {
      const dateCreated = searchOption.dateCreated;
      const dateValue: number[] = [];
      const timeZone = new Date().getTimezoneOffset() * 60 * 1000;
      dateValue[0] = new Date(dateCreated[0]).getTime() + timeZone;
      dateValue[1] = new Date(dateCreated[1]).getTime() + timeZone;
      dateCreatedQuery = `${dateCreatedQuery} and date_created gte "${dateValue[0]}" and date_created lt "${dateValue[1]}"`;
    }
    // handle date updated
    var dateUpdatedQuery = "";
    if (!(searchOption.dateUpdated === undefined)) {
      const dateUpdated = searchOption.dateUpdated;
      const dateValue: number[] = [];
      const timeZone = new Date().getTimezoneOffset() * 60 * 1000;
      dateValue[0] = new Date(dateUpdated[0]).getTime() + timeZone;
      dateValue[1] = new Date(dateUpdated[1]).getTime() + timeZone;
      dateUpdatedQuery = `${dateUpdatedQuery} and last_updated gte "${dateValue[0]}" and last_updated lt "${dateValue[1]}"`;
    }
    //ToQuery
    const query = `version eq "active" and project_id in "[${projectId}]"${keyWordQuery}${subtypeQuery}${priorityQuery}${caseNameQuery}${automatedByQuery}${dateCreatedQuery}${dateUpdatedQuery}`;
    return query;
  }
  /* Sync API */
  /**
   * Get sync projects.
   */
  public async getSyncProjects(): Promise<any> {
    const url = `${this.endpoint}/api/internal/sync/projects`;
    const headers = {
      Authorization: this.accessToken,
      "Content-type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    return response.data;
  }
  /**
   * Get sync cases.
   */
  public async getSyncCases(): Promise<any> {
    const url = `${this.endpoint}/api/internal/sync/cases`;
    const headers = {
      Authorization: this.accessToken,
      "Content-type": "application/json"
    };
    const config = { headers };
    const response = this.httpClient.get(url, config);
    return response;
  }
  /* Get Store API */
  /**
   * Get store project by project id.
   * @param projectId
   */
  public async getStoreProjectById(projectId: number): Promise<any> {
    const url = `${this.endpoint}/api/internal/storage/project/${projectId}`;
    const headers = {
      Authorization: this.accessToken,
      "Content-type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    return response.data;
  }
  /**
   * Get global data.
   * @return {Promise<IGlobalData>}
   */
  public async getGlobalData(): Promise<IGlobalData> {
    const url = `${this.endpoint}/projects/static`;
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    return response.data;
  }
  /**
   * Search for suggest cases by query.
   * @param pattern
   * @param limit
   */
  public async getSuggestTestCases(
    pattern: string,
    limit: number = 10
  ): Promise<IGetSuggestTestCasesResponseDTO> {
    const url = `${this.endpoint}/api/v1/suggest/cases?search=${pattern}&limit=${limit}`;
    const headers = {
      Authorization: this.accessToken,
      "Content-Type": "application/json"
    };
    const config = { headers };
    const response = await this.httpClient.get(url, config);
    return response.data;
  }
  public async generateInfosByKey(key: string): Promise<any> { }
  /**
   * Generate template info by key according to giving template
   * @param key
   * @param templatePath
   */
  public async generateInfoByKeyInTemplate(
    key: string,
    templatePath: string
  ): Promise<string> {
    const fs = require("fs");
    const responseData = await this.getTestCaseByKey(key);
    //read template
    var templateInfo = fs.readFileSync(templatePath, "utf8");
    //1.handle basic info
    //extract match fields that need to fill
    const matchRes = templateInfo.match(/.*[\n\t\s]*\$\{.*?\}[\n\t\s]*.*/g);

    //fill template info
    for (var i = 0; i < matchRes.length; i++) {
      const field = matchRes[i].split(":")[0].trim();
      // logger.debug(field);
      //get info
      const info = await this.fetchTemplateFieldInfo(responseData, field);
      //replace
      if (!(info === undefined)) {
        templateInfo = await templateInfo.replace(/\$\{.*?\}/, info);
      }
    }
    //2.handle test steps info
    const matchResTs = templateInfo.match(/.*\$\{.*?\}.*[\n\s\t\r]*.*/g);
    var childrenLen = responseData.children.length;
    var contentInfo = `${matchResTs[0]}\n${matchResTs[1]}\n`;
    // expand info
    var expandInfo = "";
    for (var i = 0; i < childrenLen; i++) {
      expandInfo = `${expandInfo}${contentInfo}`;
    }
    templateInfo = templateInfo.replace(
      `${matchResTs[0]}\n${matchResTs[1]}`,
      expandInfo
    );
    //extract testSteps info
    var steps = [];
    var expectedResult = [];
    if (childrenLen != 0) {
      for (var i = 0; i < childrenLen; i++) {
        steps[i] = await this.stringFilter(responseData.children[i].name);
        expectedResult[i] = await this.stringFilter(
          responseData.children[i].expectedResult
        );
      }
    }
    //fill test steps
    for (var i = 0; i < childrenLen; i++) {
      templateInfo = templateInfo
        .replace("${}", steps[i])
        .replace("${}", expectedResult[i]);
    }
    return templateInfo;
  }
  public async getTestStepsInfo() { }
  /**
   * Fetch template field information by field name
   * To fill template
   * @param responseData
   * @param field
   */
  public async fetchTemplateFieldInfo(
    responseData: any,
    field: string
  ): Promise<any> {
    const key = `${responseData.prefix}-${responseData.externalId}`;
    // logger.debug(matchInfo);
    // var accountType = (info[0].split(":"))[1].trim();
    // var entryPoint = (info[1].split(":")[1]).trim();

    if (field == "suite") {
      const ascendantsLen = responseData.ascendants.length;
      const suiteInfo = responseData.ascendants[ascendantsLen - 1].name;
      return suiteInfo;
    } else if (field == "name") {
      const name = `${key}:${responseData.name}`;
      return name;
    } else if (field == "keywords") {
      const valueLen = responseData.values.length;
      const keywords = [];
      for (var i = 0; i < valueLen; i++) {
        // 224 is keywords field id
        if (responseData.values[i].fieldId == 224) {
          keywords.push(responseData.values[i].name);
        }
      }
      // var keywordsStr = await this.convertoString(keywords);
      // return keywordsStr;
      return keywords;
    } else if (field == "priority") {
      const casePrioriy = `P${responseData.priority}`;
      return casePrioriy;
    } else if (field == "maintainers") {
      const valueLen = responseData.values.length;
      const maintainers = [];
      for (var i = 0; i < valueLen; i++) {
        // 6615030 is keywords field id
        if (responseData.values[i].fieldId == 6615030) {
          maintainers.push(responseData.values[i].name);
        }
      }
      // var maintainersStr = await this.convertoString(maintainers);
      // return maintainersStr;
      return maintainers;
    } else if (field == "docs") {
      var docsArr = [];
      var url = await `${this.endpoint}/test-cases/${key}`;
      docsArr.push(url);
      var docs = this.convertoString(docsArr);
      return docs;
    } else if (field == "ids") {
      const ids = key;
      return ids;
    } else if (field == "subtype") {
      const valueLen = responseData.values.length;
      const subtype = [];
      for (var i = 0; i < valueLen; i++) {
        // 6231852 is keywords field id
        if (responseData.values[i].fieldId == 6231852) {
          subtype[i] = responseData.values[i].name;
        }
      }
      var subtypeStr = await this.convertoString(subtype);
      return subtypeStr;
    } else if (field == "testSteps") {
      const children = responseData.children;
      const childrenLen = children.length;
      var testStepsInfo: stepsInfo[] = [];
      for (var i = 0; i < childrenLen; i++) {
        testStepsInfo[i] = {
          step: await this.stringFilter(children[i].name),
          expectedResult: await this.stringFilter(children[i].expectedResult)
        };
      }
      return testStepsInfo;
    } else if (field == "ascendants") {
      const ascendants = responseData.ascendants;
      var suiteTree = "";
      for (var i = 0; i < ascendants.length; i++) {
        var tempName = ascendants[i].name;
        // delete space
        const space = new RegExp(" ", "g");
        tempName = tempName.replace(space, "");
        suiteTree = `${suiteTree}/${tempName}`;
      }
      suiteTree = suiteTree.replace(suiteTree.charAt(0), "");
      return suiteTree;
    }
    //handle preconditions
    var preconditions = await this.stringFilter(responseData.preconditions);
    const matches = preconditions.match(/Account type\(\/s\)\:[ ]*.*/);
    var matchInfo: any[];
    //no matches
    if (matches === undefined || matches === null) {
      return;
    } else {
      //match by special (/s):
      matchInfo = matches[0].split("(/s):");
    }
    if (field == "preconditions") {
      return preconditions;
    } else if (field == "accountType") {
      //return accountType
      const accountType = matchInfo[1].replace("Extension type", "").trim();
      return accountType;
    } else if (field == "extensionType") {
      //return extensionType
      const extensionType = matchInfo[2].replace("Entry point", "").trim();
      return extensionType;
    } else if (field == "entryPoint") {
      //return entryPointStr
      const entryPointStr = matchInfo[3].trim();
      return entryPointStr;
    }
  }
  /**
   * Convert original array to string
   * @param original
   */
  private async convertoString(original: any[]): Promise<string> {
    var result = "";
    for (var i = 0; i < original.length; i++) {
      result = `${result}"${original[i]}",`;
    }
    result = result.substring(0, result.length - 1);
    return result;
  }
  /**
   * Generate template information by key.
   * @param key Case key
   */
  public async generateTemplatesInfoByKey(key: string): Promise<string> {
    const responseData = await this.getTestCaseByKey(key);
    const ascendantsLen = responseData.ascendants.length;
    const suite = responseData.ascendants[ascendantsLen - 1].name;
    const nameInfo = `${key}:${responseData.name}`;
    const casePrioriy = `P${responseData.priority}`;
    const skip = true; //default
    const skipReason = "waiting for E2E deliver"; //default
    const keywords = [];
    const maintainers = [];
    const valueLen = responseData.values.length;
    //Refer to field id.
    for (var i = 0; i < valueLen; i++) {
      if (responseData.values[i].fieldId == 224) {
        keywords.push(responseData.values[i].name);
      } else if (responseData.values[i].fieldId == 6615030) {
        maintainers.push(responseData.values[i].name);
      }
    }
    //Handle the test steps info.
    var childrenLen;
    var steps = [];
    var expectedResult = [];
    if ((childrenLen = responseData.children.length) != 0) {
      for (var i = 0; i < childrenLen; i++) {
        steps[i] = await this.stringFilter(responseData.children[i].name);
        expectedResult[i] = await this.stringFilter(
          responseData.children[i].expectedResult
        );
      }
    }
    //some str to connect the data.
    const importInfo = `import { h, runTest } from '@src/init';\nimport { App } from "@src/pageModels/app";\nimport { StepWrapper } from "@tests/stepWrapper";\nimport { HomePageController } from "@src/pageControllers/app/homePage";\nimport MeetingRoomController from "@src/pageControllers/app/homePage/meetingRoom";\nimport { WebphoneSession } from "ultron-client";\nimport { LeftRailController } from "@src/pageControllers/app/homePage/meetTab";\nimport MeetingPageController from '@src/pageControllers/app/homePage/meetingPage';\nimport { sleep } from "@src/libs/utils";\n`;
    const runTestInfo = "runTest({\n";
    const asyncInfo = "},async(t:any)=>{\n";
    const preStep = '\tawait h(t).When_("';
    const afterStep = ",async()=>{\n\t});\n";
    const preExpect = '\tawait h(t).Then_("';
    const afterExpect = ",async() => {\n\t});\n";
    const end = "\th(t).finish();\n});";
    //storage template info.
    var resInfo: TemplateInfo = {
      suite: suite,
      name: [nameInfo],
      priority: casePrioriy,
      skip: skip,
      skipReason: skipReason,
      keywords: keywords,
      maintainers: maintainers
    };
    // handle resInfo
    var resInfoStr = JSON.stringify(resInfo);
    const resInfoKeys = Object.keys(resInfo); //get keys
    for (var i = 0; i < resInfoKeys.length; i++) {
      resInfoStr = resInfoStr.replace(`"${resInfoKeys[i]}"`, resInfoKeys[i]);
      resInfoStr = resInfoStr.replace(
        `,${resInfoKeys[i]}`,
        `,\n${resInfoKeys[i]}`
      );
    }
    // delete the character of start and end
    resInfoStr = resInfoStr.replace(resInfoStr.charAt(0), "");
    resInfoStr = resInfoStr.replace(
      resInfoStr.charAt(resInfoStr.length - 1),
      ""
    );
    // add tab
    for (var i = 0; i < resInfoKeys.length; i++) {
      resInfoStr = resInfoStr.replace(
        `${resInfoKeys[i]}`,
        `\t${resInfoKeys[i]}`
      );
    }
    // put together
    var resultInfo = `${importInfo}${runTestInfo}${resInfoStr}\n${asyncInfo}`;
    for (var i = 0; i < childrenLen; i++) {
      resultInfo = `${resultInfo}${preStep}${steps[i]}"${afterStep}${preExpect}${expectedResult[i]}"${afterExpect}`;
    }
    resultInfo = `${resultInfo}${end}`;
    return resultInfo;
  }

  /**
   * Write info to ouputstream and storage to file path.
   * @param info
   * @param filePath
   */
  public async writeTemplateInfo(info: string, filePath: string): Promise<any> {
    var fs = require("fs");
    var infoWriteStream = fs.createWriteStream(filePath);
    infoWriteStream.write(info, "UTF-8");
    infoWriteStream.end();
    infoWriteStream.on(`${filePath}--Done`, () => {
      logger.debug("${filePath}--Done");
    });
    // infoWriteStream.on('error',function(err:any){
    //     logger.debug(err.stack);
    // });
  }
  /**
   * Store a template file to file path by case key.
   * @param key
   * @param filePath
   */
  public async storeTemplateInfoByCaseKey(key: string, filePath: string) {
    const info = await this.generateTemplatesInfoByKey(key);
    filePath = `${filePath}${key}.test.ts`;
    this.writeTemplateInfo(info, filePath);
  }

  /**
   * Store template info for file tree by case key.
   * @param key
   * @param filePath
   */
  private async storeFileInTreeByCaseKey(
    key: string,
    filePath: string,
    templatePath?: string
  ) {
    const response = await this.getTestCaseByKey(key);
    const ascendants = response.ascendants;
    var suiteTree = "";
    for (var i = 0; i < ascendants.length; i++) {
      var tempName = ascendants[i].name;
      // delete space
      const space = new RegExp(" ", "g");
      tempName = tempName.replace(space, "");
      suiteTree = `${suiteTree}/${tempName}`;
    }
    suiteTree = suiteTree.replace(suiteTree.charAt(0), "");
    filePath = `${filePath}${suiteTree}`;
    this.writeInfoByCreateDir(key, filePath, templatePath);
  }

  /**
   * Store file by case key
   * @param key
   * @param filePath
   * @param templatePath
   */
  public async storeFileByCaseKey(
    key: string,
    filePath: string,
    templatePath?: string
  ) {
    const response = await this.getTestCaseByKey(key);
    const caseInfo = await this.fillCaseInfoMap(key, response);
    const fs = require("fs");
    var templateInfo;
    //make sure template path is true
    if (templatePath === undefined) {
      templateInfo = fs.readFileSync(
        "../templates/Default.txt",
        "UTF-8",
        function (err: any) {
          throw new Error("Can't not find default.txt");
        }
      );
    } else {
      templateInfo = fs.readFileSync(templatePath, "UTF-8", function (err: any) {
        throw new Error(`Can't not find ${templatePath}`);
      });
    }
    await this.storeFileInTree(caseInfo, filePath, templateInfo);
  }
  /**
   * Store and fill templateInfo
   * @param caseInfo
   * @param filePath
   * @param templateInfo
   */
  async storeFileInTree(
    caseInfo: CaseInfo,
    filePath: string,
    templateInfo: string
  ): Promise<any> {
    var resultInfo = templateInfo;
    filePath = `${filePath}${caseInfo.ascendants}`;
    var fileName = `${filePath}/${caseInfo.id}.test.ts`;
    //use mustache to render template
    const Mustache = require("mustache");
    Mustache.parse(templateInfo);
    resultInfo = Mustache.render(templateInfo, caseInfo);
    // logger.debug(resultInfo);
    // logger.debug(filePath);
    //write resultInfo into path
    await this.writeInfo(resultInfo, filePath, fileName);
  }
  /**
   * Write info by create dir.
   * @param info
   * @param filePath
   */
  private async writeInfoByCreateDir(
    key: string,
    filePath: string,
    templatePath?: string
  ): Promise<any> {
    // get template info
    var info;
    if (!(templatePath === undefined)) {
      info = await this.generateInfoByKeyInTemplate(key, templatePath);
    } else {
      info = await this.generateTemplatesInfoByKey(key);
    }
    // create directory
    const mkdirp = require("mkdirp");
    await mkdirp(filePath); //.then((made: any) => logger.debug(`Create in ${made}`));
    // store info
    var fs = require("fs");
    const testFile = `${filePath}/${key}.test.ts`;
    logger.debug(`Store ${testFile}`);
    var infoWriteStream = await fs.createWriteStream(testFile);
    await infoWriteStream.write(info, "UTF-8");
    await infoWriteStream.end();
    infoWriteStream.on(`${filePath}--Done`, () => {
      logger.debug("${filePath}--Done");
    });
    infoWriteStream.on("error", function (err: any) {
      logger.debug(err.stack);
    });
  }
  private async writeInfo(
    info: string,
    filePath: string,
    fileName: string
  ): Promise<any> {
    const mkdirp = require("mkdirp");
    await mkdirp(filePath); //.then((made: any) => logger.debug(`Create in ${made}`));
    // store info
    var fs = require("fs");
    logger.debug(`Store ${fileName}`);
    var infoWriteStream = await fs.createWriteStream(fileName);
    await infoWriteStream.write(info, "UTF-8");
    await infoWriteStream.end();
    infoWriteStream.on(`${fileName}--Done`, () => {
      logger.debug("${fileName}--Done");
    });
    infoWriteStream.on("error", function (err: any) {
      logger.debug(err.stack);
    });
  }
  /**
   * Make directory.
   * @param dir
   */
  private async mkdir(dir: any) {
    const fs = require("fs");
    fs.mkdir(dir, (err: any) => {
      if (err) {
        logger.debug(err);
        return;
      }
    });
  }
  /**
   * Filter string with special character.
   * @param str
   */
  private async stringFilter(str: string): Promise<any> {
    if (str != null) {
      const newLine = "\n[\n]*";
      const quota = '"';
      str = await str
        .replace(new RegExp("<.*?>", "g"), "")
        .replace(new RegExp(newLine, "g"), " ")
        .replace(new RegExp(quota, "g"), "'"); // RegExp = "\<.*?\>" all block
      str = str.trim();
      str = unescape(str); //url
      // str = decodeURIComponent(str);//uri
      str = await this.htmlDecodeByRegExp(str); //html tags
    }
    return str;
  }
  private async htmlDecodeByRegExp(str: string): Promise<string> {
    if (str.length == 0) return "";
    str = await str
      .replace(/&amp;/g, "&")
      .replace(/&lt;/g, "<")
      .replace(/&gt;/g, ">")
      .replace(/&nbsp;/g, " ")
      .replace(/&#39;/g, "'")
      .replace(/&quot;/g, '"');
    return str;
  }
}
// // /**
// //  * Test Modules
// //  */
// async function testitDemo() {
//   /* Login require information */
//   const TESTIT_ENDPOINT = "https://testit.ringcentral.com";
//   const testItSdk = new TestItSDK(TESTIT_ENDPOINT);
//   // const username: string  = process.env.USERNAME;
//   // const password: string  = process.env.PASSWORD;

//   //   await testItSdk.searchCasesBySearchOptions(searchOption);

//   /* 1、Test login function. */
//   // logger.debug(USERNAME);
//   // logger.debug(USERNAME);
//   //config my debug
//   const log4js = require('log4js');
//   log4js.configure({
//       appenders: { console: { type: 'console' } },
//       categories: { default: { appenders: ['console'], level: 'debug' } }
//   });
//   await testItSdk.login(USERNAME, PASSWORD);
//   const searchOption: SearchOptions = {
//     projectName: "Jupiter",
//     subtype:["E2E"],
//     dateCreated :["2021-01-01","2021-01-10"],
//     dateUpdated :["2021-01-01","2021-01-10"]
//   };

//   //   var output = Mustache.render("{{title}} sßpends {{calc}}", view);
//   //   logger.debug(output);
//   const path = "../templateFiles/";
//   const templatePath = "../templates/JupiterTemplate.ts.txt";
//   // await testItSdk.searchCasesBySearchOptions(searchOption);
//   // await testItSdk.storeFileByCaseKey("JPT-5660",path,templatePath);
//   // await testItSdk.searchCasesBySearchOptions(searchOption);
//   await testItSdk.storeFilesBySearchOptions(searchOption, path, templatePath);
// }

// testitDemo();
