/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const https = require('https');
const { StatusCode, StatusMessages } = require('./reporter');

async function mergeDataBetweenVersion(oldVersion, newVersion, allVersionUrl) {
  const oldVersionNumber = oldVersion.replace(/\./g, '');
  const newVersionNumber = newVersion.replace(/\./g, '');
  const url = allVersionUrl.replace('allVersion.json', '');
  let versionArr = [];
  await downloadFiles(allVersionUrl).then((versionNumbers) => {
    versionArr = JSON.parse(versionNumbers);
  }).catch(err => {
    console.log('ERROR CODE:', err.code);
  });

  if (versionArr.length === 0) {
    return [];
  }

  for (let i = 0; i < versionArr.length; i++) {
    const version = versionArr[i].replace(/\./g, '');
    if (version < oldVersionNumber || version > newVersionNumber) {
      versionArr.splice(i, 1);
      i--;
    }
  }
  let orderVersionArr = versionArr.sort((a, b) => {
    return a.replace(/\.|.json/g, '') - b.replace(/\.|.json/g, '');
  });
  return formatExcelData(orderVersionArr, url);
}

function downloadFiles(allVersionUrl) {
  return new Promise((resolve, reject) => {
    getVersionsData(allVersionUrl, (versions) => {
      if (versions) {
        resolve(versions);
      } else {
        reject({ code: -1 });
      }
    });
  });
}

function getLink(url, fileName) {
  return `${url}${fileName}.json`;
}

function getVersionsData(versionUrl, callback) {
  let json = '';
  const SUCCESS_CODE = 200;
  const request = https.get(versionUrl, { timeout: 2000 }, function (res) {
    if (res.statusCode !== SUCCESS_CODE) {
      return;
    }
    res.on('data', function (d) {
      json += d;
    });
  }).on('error', function (e) {
    console.log('ERROR:', e.message);
  }).on('close', () => {
    callback(json);
  }).on('timeout', () => {
    request.destroy();
  });
}

/**
 * 将最老版本changelog中函数有变化的数据作为初始数据,去跟其他版本的changelog数据进行合并。
 * @param {Array} orderVersionArr 
 * @param {String} url 
 * @returns {Array}
 */
async function formatExcelData(orderVersionArr, url) {
  let allMergeData = [];
  const versionUrl = getLink(url, orderVersionArr[0]);
  let oldestVersionData = [];
  const MAX_LENGTH = 2;
  await downloadFiles(versionUrl).then(versionData => {
    oldestVersionData = JSON.parse(versionData);
  }).catch(err => {
    console.log('ERROR CODE:', err.code);
  });

  if (orderVersionArr.length < MAX_LENGTH) {
    return allMergeData;
  }

  oldestVersionData.forEach(oldestData => {
    if (oldestData.newApi !== oldestData.oldApi) {
      oldestData.version = orderVersionArr[0];
      allMergeData.push(oldestData);
    }
  });
  await mergeAllData(orderVersionArr, url, allMergeData);
  return allMergeData;
}

/**
 * 将初始数据挨个跟下一个版本的changelog数据进行比较合并。
 * 
 * @param {Array} orderVersionArr 
 * @param {String} url 
 * @param {Array} allMergeData 
 */
async function mergeAllData(orderVersionArr, url, allMergeData) {
  let index = 1;
  while (index < orderVersionArr.length) {
    const versionUrl = getLink(url, orderVersionArr[index]);
    let newVersionData = [];
    await downloadFiles(versionUrl).then(versionData => {
      newVersionData = JSON.parse(versionData);
    }).catch(err => {
      console.log('ERROR CODE:', err.code);
    });
    mergeTwoVersionData(allMergeData, newVersionData, orderVersionArr[index]);
    index++;
  }

}

/**
 * 跟下一个版本的数据进行合并
 * 
 * @param {Array} allMergeData 
 * @param {Array} newVersionData 
 */
function mergeTwoVersionData(allMergeData, newVersionData, currentVersion) {
  let indexListSet = new Set();
  for (let i = 0; i < allMergeData.length; i++) {
    const data = allMergeData[i];
    for (let j = 0; j < newVersionData.length; j++) {
      if (!indexListSet.has(j) && data.newDtsName === newVersionData[j].oldDtsName &&
        compareApiText(data.newApi, newVersionData[j].oldApi,) && data.newApi !== data.oldApi) {
        allMergeData[i].newApi = newVersionData[j].newApi;
        allMergeData[i].version = currentVersion;
        indexListSet.add(j);
      }
    }
  }

  newVersionData.forEach((data, index) => {
    if (!indexListSet.has(index)) {
      data.version = currentVersion;
      allMergeData.push(data);
    }
  });
}

/**
 * 比较旧版本的newApi和新版本的oldApi,判断是否一样
 * 
 * @param {string} oldApiText 
 * @param {string} newApiText 
 * @returns {Boolean}
 */
function compareApiText(oldApiText, newApiText) {
  if (formatApi(oldApiText) === formatApi(newApiText) && formatApi(oldApiText) !== '') {
    return true;
  }
  return false;
}

/**
 * 格式化API定义内容,排除空格,换行,符号对比较API的影响。
 * 
 * @param {string} apiText 
 * @returns {string}
 */
function formatApi(apiText) {
  if (!apiText) {
    return '';
  }
  return apiText.replace(/\r|\n|\s+|\,|\;/g, '');
}

/**
 * 将数组形式的changelog数据转换成Map
 * 
 * @param {Array} dataInChangelogs 
 * @returns {Map}
 */
function covertToMap(dataInChangelogs) {
  let dataMap = new Map();
  dataInChangelogs.forEach(data => {
    const dataSignature = getSignature(data.newDtsName, data.oldApi);
    if (!dataMap.get(dataSignature)) {
      dataMap.set(dataSignature, [data]);
    } else {
      const dataArr = dataMap.get(dataSignature);
      dataArr.push(data);
      dataMap.set(dataSignature, dataArr);
    }
  });
  return dataMap;
}

function getSignature(dtsName, apiText) {
  const handledDtsName = handleDtsName(dtsName).dtsPath;
  return `${handledDtsName}#${formatApi(apiText)}`;
}

function addChangelogLink(changelogsData, diffsData, diffs) {
  diffsData.forEach(diffData => {
    changelogsData.forEach(changelogData => {
      diffData.changelogs.add({
        version: changelogData.version,
        url: changelogData.changelog
      });

      if (changelogData.oldType !== changelogData.newType) {
        diffData.status = StatusMessages[StatusCode.CLASS_CHANGES];
        diffData.StatusCode = StatusCode.CLASS_CHANGES;
        diffData.oldMessage = changelogData.oldApi;
        diffData.newMessage = changelogData.newApi;
      }

      if (changelogData.oldDtsName !== changelogData.newDtsName) {
        diffData.status = StatusMessages[StatusCode.DTS_CHANGED];
        diffData.StatusCode = StatusCode.DTS_CHANGED;
        diffData.oldMessage = changelogData.oldDtsName;
        diffData.newMessage = changelogData.newDtsName;
      }

      if (changelogData.newApi === changelogData.oldApi || changelogData.newApi === 'N/A') {

        return;
      }

      if (diffData.statusCode === StatusCode.DELETE) {
        const newApiSignature = getSignature(changelogData.newDtsName, changelogData.newApi);
        diffs.delete(newApiSignature);
        diffData.status = StatusMessages[StatusCode.FUNCTION_CHANGES];
        diffData.StatusCode = StatusCode.FUNCTION_CHANGES;
        diffData.oldMessage = changelogData.oldApi;
        diffData.newMessage = changelogData.newApi;
      } else if (diffData.statusCode === StatusCode.DELETE_CLASS) {
        diffData.status = StatusMessages[StatusCode.CLASS_CHANGES];
        diffData.StatusCode = StatusCode.CLASS_CHANGES;
        diffData.oldMessage = changelogData.oldApi;
        diffData.newMessage = changelogData.newApi;
      }
    });
  });
  return diffsData;
}

function mergeDiffsAndChangelogs(changelogs, diffs) {
  changelogs.forEach((data, dataSignature) => {
    const diffsData = diffs.get(dataSignature);
    if (diffsData) {
      diffs.set(dataSignature, addChangelogLink(data, diffsData, diffs));
      changelogs.delete(dataSignature);
      return;
    } 
    data.forEach(changelogData => {
      const newApiSignature = getSignature(changelogData.newDtsName, changelogData.newApi);
      const diffsData = diffs.get(newApiSignature);
      if (!diffsData) {
        return;
      }
      diffsData.forEach(diffData => {
        diffData.changelog.add(changelogData.changelog);
      });
      changelogs.delete(dataSignature);
    });
    
  });

  changelogs.forEach((changelogData, signature) => {
    changelogData.forEach(changelogApi => {
      if (diffs.get(signature)) {
        diffs.get(signature).push(formatChangelogApi(changelogApi));
      } else {
        diffs.set(signature, [formatChangelogApi(changelogApi)]);
      }
    });
  });

  return diffs;
}

function formatChangelogApi(changelogApi) {
  const filePathObj = handleDtsName(changelogApi.newDtsName);
  return {
    packageName: '',
    className: changelogApi.newType,
    rawText: changelogApi.newApi,
    dtsName: filePathObj.dtsPath,
    hint: '',
    changelogs: [{
      version: changelogApi.version,
      url: changelogApi.changelog
    }],
    statusCode: StatusCode.CHANGELOG,
    status: StatusMessages[StatusCode.CHANGELOG],
    oldMessage: changelogApi.oldApi,
    newMessage: changelogApi.newApi
  };
}

function handleDtsName(dtsName) {
  let packageName = dtsName;
  let dtsPath = dtsName;
  if (dtsName.indexOf('api/@internal/component/ets') > -1) {
    packageName === 'ArkUI';
    dtsPath.replace('api/@internal/component/ets', 'component');
  } else if (dtsName.indexOf('api/@internal/ets') > -1) {
    packageName = dtsName.replace('api/@internal/ets', 'api/@internal/full');
    dtsPath = packageName;
  }
  return { packageName, dtsPath };
}


async function mergeDiffWithChangeLog(diffs, oldVersion, newVersion, allVersionUrl) {
  const dataInChangelogs = await mergeDataBetweenVersion(oldVersion, newVersion, allVersionUrl);
  const dataMapInChangelogs = covertToMap(dataInChangelogs);
  return mergeDiffsAndChangelogs(dataMapInChangelogs, diffs);
}

exports.applyChangeLogs = mergeDiffWithChangeLog;