const fs = require('fs');
const fsExtra = require('fs-extra');
const path = require('path');
const http = require('http');
const electron = require('electron');
const app = (electron.remote || electron).app;

const packageJsonPath = process.env.NODE_ENV === 'development' ? path.join(process.cwd(), 'package.json') : path.join(process.resourcesPath, './app.asar.unpacked/package.json');
const packData = fs.readFileSync(packageJsonPath);
const { dependencies, name } = JSON.parse(packData);

function CheckApp({ appDownDir, version, log, start, end }) {
  const { serverIP } = this.getLocalConfig();
  this.serverIP = serverIP;
  this.modules = {};
  this.appCode = name.toLocaleUpperCase();
  this.localVersion = app.getName(0);
  this.log = log || global.ktLog['i'];
  this.remoteVersion = version || '';
  this.end = end || (() => {});
  this.start = start || (() => {});

  let userDataPath = app.getPath('userData');
  this.configPath = path.join(path.dirname(userDataPath), `${this.appCode.toLowerCase()}_config.json`);
  this.baseUrl = appDownDir ? appDownDir.replace(`${version}.zip`, '') : `http://${serverIP}:10020/${this.appCode}/app/programs`;
  this.resourcesDir = process.resourcesPath;
}
/* 获取本地配置 */
CheckApp.prototype.getLocalConfig = function () {
  if (global.configApi && global.configApi.getLocalConfig) return global.configApi.getLocalConfig();
  if (!fs.existsSync(this.configPath)) return { serverIP: '172.19.8.211' };
  const data = fs.readFileSync(this.configPath, { encoding: 'UTF-8' });
  if (!data) return { serverIP: '172.19.8.211' };
  const json = JSON.parse(data);
  return json || { serverIP: '172.19.8.211' };
};

/* 获取远程版本号 */
CheckApp.prototype.getRemoteVersion = function () {
  return new Promise((resolve) => {
    const getUrl = `${this.baseUrl}/update.json`;

    this.log(`检测服务端APP版本，URL=${getUrl}`);
    let timer = null;
    const req = http
      .get(getUrl, (res) => {
        let data = '';
        res.on('data', (chunk) => {
          timer && clearTimeout(timer);
          data += chunk;
        });
        res.on('end', () => {
          if (res.statusCode === 200 && data && data.indexOf('productName') > -1) {
            this.log(`本地APP版本=${this.localVersion}`);
            this.log(`服务端APP版本=${data}`);

            data = JSON.parse(data);
            this.remoteVersion = data.productName;
            this.modules = data.dependencies;
            return resolve(data.productName);
          }
          resolve('');
        });
      })
      .on('error', (err) => {
        timer && clearTimeout(timer);
        this.log(`远程版本获取失败：${err}`);
        this.remoteVersion = '';
        this.modules = {};
        resolve('');
      });
    timer = setTimeout(() => {
      req.destroy();
      this.log(`远程版本获取超时`);
    }, 2000);
  });
};

/* 检测是否有新版本 */
CheckApp.prototype.checkVersion = function () {
  return new Promise((resolve) => {
    try {
      const notLimit = !!this.remoteVersion;
      this.getRemoteVersion().then((version) => {
        const value = this.compareVersion(version, this.localVersion);
        resolve(value > 0 || (version && notLimit) ? version : '');
      });
    } catch (e) {
      this.log(`checkVersion执行异常=${e}`);
      resolve('');
    }
  });
};

/* 比较版本大小 v1:远程版本 v2:本地版本 */
CheckApp.prototype.compareVersion = function (version1, version2) {
  try {
    if (!version1 || !version2) return 0;
    if (version1 === version2) return 0;
    // 开发环境不下载包
    if (version2 === 'Electron') return 1;

    let arr1 = version1.split(`${this.appCode}_`);
    let arr2 = version2.split(`${this.appCode}_`);
    let ver1 = arr1[1] || arr1[0];
    let ver2 = arr2[1] || arr1[0];
    let v1 = ver1.split('.').map(Number);
    let v2 = ver2.split('.').map(Number);
    for (let i = 0; i < v1.length || i < v2.length; i++) {
      let part1 = v1[i] || 0;
      let part2 = v2[i] || 0;
      if (part1 > part2) return 1; // 如果第一个版本的一部分比第二个大
      if (part1 < part2) return -1; // 反之
    }

    // 比较beta版本
    v1 = version1.split(`_${this.appCode}`)[0];
    v2 = version2.split(`_${this.appCode}`)[0];
    v1 = +v1.replace(/\D/gm, '');
    v2 = +v2.replace(/\D/gm, '');
    return v1 === 0 ? 1 : v2 === 0 ? -1 : v1 - v2 > 0 ? 1 : -1;
  } catch (e) {
    this.log(`compareVersion执行异常=${e}`);
    return 0;
  }
};
/* 下载 */
CheckApp.prototype.downLoadFunc = function () {
  return new Promise(async (resolve) => {
    try {
      const zipPath = path.join(this.resourcesDir, `${this.remoteVersion}.zip`);
      if (fs.existsSync(zipPath)) return resolve(true);

      const downPath = `${this.baseUrl}/${this.remoteVersion}.zip`;
      const Downloader = require('nodejs-file-downloader');
      this.log(`版本下载${downPath}=>${this.resourcesDir}`);
      const downloader = new Downloader({
        url: downPath,
        directory: this.resourcesDir,
        timeout: 1000 * 60 * 5,
      });
      await downloader.download();

      const mdlJson = this.modules;
      const mdlArr = Object.keys(mdlJson);
      // 依赖包下载
      if (mdlArr.length > 0) {
        const upJson = {};
        const promiseArr = [];
        for (const mdl of mdlArr) {
          // 依赖包需要更新
          if (this.compareVersion(mdlJson[mdl], dependencies[mdl]) === 1) {
            const name = mdl.replace('/', '__');
            const url = `${this.baseUrl}/${name}.zip`;
            const dwn = new Downloader({ url, directory: this.resourcesDir });
            upJson[mdl] = mdlJson[mdl];
            promiseArr.push(dwn.download());
          }
        }
        for (const fn of promiseArr) {
          await fn;
        }
        this.log(`依赖包${JSON.stringify(upJson)}下载完成`);
      }

      this.log(`${this.remoteVersion}版本下载完成`);
      resolve(true);
    } catch (e) {
      this.log(`downLoadFunc执行异常=${e}`);
      resolve(false);
    }
  });
};

/* 获取应用更新目录-以版本号命名 */
CheckApp.prototype.getAppDirName = function () {
  return new Promise((resolve) => {
    try {
      const jsonFile = path.join(this.resourcesDir, './app.json');
      let appDirName = this.remoteVersion;
      if (fs.existsSync(jsonFile)) {
        let json = fs.readFileSync(jsonFile);
        if (json) {
          json = JSON.parse(json);
          appDirName = json.name === this.remoteVersion ? `KT_${this.remoteVersion}` : this.remoteVersion;
        }
      }
      resolve(appDirName);
    } catch (e) {
      this.log(`getAppDirName执行异常=${e}`);
      resolve('');
    }
  });
};

/* 解压 */
CheckApp.prototype.extractFunc = function (appDir) {
  return new Promise(async (resolve) => {
    try {
      const zipPath = path.join(this.resourcesDir, `${this.remoteVersion}.zip`);
      const targetDir = path.join(this.resourcesDir, `./${appDir}`);
      this.log(`版本解压${zipPath}->${targetDir}`);
      try {
        await this.extractAllToAsync(zipPath, targetDir);
        this.log(`${this.remoteVersion}版本解压完成`);
      } catch (error) {
        this.log(`extractFunc解压执行异常=${error}`);
        resolve(false);
      }
      const mdlJson = this.modules;
      const mdlArr = Object.keys(mdlJson);
      // 依赖包解压
      if (mdlArr.length > 0) {
        const promiseArr = [];
        const upJson = {};
        for (const mdl of mdlArr) {
          const name = mdl.replace('/', '__');
          const mdlZip = path.join(this.resourcesDir, `${name}.zip`);
          if (fs.existsSync(mdlZip)) {
            upJson[mdl] = mdlJson[mdl];
            const mdlTarget = path.join(targetDir, `./app.asar.unpacked/node_modules/${mdl}`);
            promiseArr.push(this.extractAllToAsync(mdlZip, mdlTarget));
          }
        }
        for (const fn of promiseArr) {
          await fn;
        }
        this.log(`依赖包解压完成${JSON.stringify(upJson)}`);
      }

      resolve(true);
    } catch (e) {
      this.log(`extractFunc执行异常=${e}`);
      resolve(false);
    }
  });
};
/* 拷贝替换资源文件包 */
CheckApp.prototype.copyMainFile = function (appDir) {
  return new Promise((resolve) => {
    try {
      // 增量包可以不携带依赖包，需要将旧依赖包拷贝到新版本中
      const unPack = path.join(this.resourcesDir, `./app.asar.unpacked/node_modules`);
      const unPackTar = path.join(this.resourcesDir, `./${appDir}/app.asar.unpacked/node_modules`);
      // 旧依赖拷贝到新包中-不覆盖
      if (fs.existsSync(unPack)) {
        fsExtra.copySync(unPack, unPackTar, { overwrite: false });
      }

      // 增量包拷贝覆盖旧项目文件
      const packageJson = path.join(this.resourcesDir, `./${appDir}/app.asar.unpacked/package.json`);
      const packageJsonTar = path.join(this.resourcesDir, `./app.asar.unpacked/package.json`);

      const mainJs = path.join(this.resourcesDir, `./${appDir}/app.asar.unpacked/electron-main.js`);
      const mainJsTar = path.join(this.resourcesDir, `./app.asar.unpacked/electron-main.js`);

      // 新依赖包拷贝覆盖旧文件
      fs.copyFileSync(packageJson, packageJsonTar);
      fs.copyFileSync(mainJs, mainJsTar);

      if (process.platform === 'win32') {
        const yml = path.join(this.resourcesDir, `./${appDir}/app-update.yml`);
        const ymlTarget = path.join(this.resourcesDir, `./app-update.yml`);

        const elevate = path.join(this.resourcesDir, `./${appDir}/elevate.exe`);
        const elevateTarget = path.join(this.resourcesDir, `./elevate.exe`);
        fs.copyFileSync(yml, ymlTarget);
        fs.copyFileSync(elevate, elevateTarget);
      }
      resolve(true);
    } catch (e) {
      this.log(`copyMainFile执行异常=${e}`);
      resolve(false);
    }
  });
};
/* 拷贝替换资源文件包 */
CheckApp.prototype.copyMainFile = function (appDir) {
  return new Promise((resolve) => {
    try {
      // 增量包可以不携带依赖包，需要将旧依赖包拷贝到新版本中
      const unPack = path.join(this.resourcesDir, `./app.asar.unpacked/node_modules`);
      const unPackTar = path.join(this.resourcesDir, `./${appDir}/app.asar.unpacked/node_modules`);
      // 旧依赖拷贝到新包中-不覆盖
      if (fs.existsSync(unPack)) {
        fsExtra.copySync(unPack, unPackTar, { overwrite: false });
      }

      // 增量包拷贝覆盖旧项目文件
      const packageJson = path.join(this.resourcesDir, `./${appDir}/app.asar.unpacked/package.json`);
      const packageJsonTar = path.join(this.resourcesDir, `./app.asar.unpacked/package.json`);

      const mainJs = path.join(this.resourcesDir, `./${appDir}/app.asar.unpacked/electron-main.js`);
      const mainJsTar = path.join(this.resourcesDir, `./app.asar.unpacked/electron-main.js`);

      // 新依赖包拷贝覆盖旧文件
      fs.copyFileSync(packageJson, packageJsonTar);
      fs.copyFileSync(mainJs, mainJsTar);

      if (process.platform === 'win32') {
        const yml = path.join(this.resourcesDir, `./${appDir}/app-update.yml`);
        const ymlTarget = path.join(this.resourcesDir, `./app-update.yml`);

        const elevate = path.join(this.resourcesDir, `./${appDir}/elevate.exe`);
        const elevateTarget = path.join(this.resourcesDir, `./elevate.exe`);
        fs.copyFileSync(yml, ymlTarget);
        fs.copyFileSync(elevate, elevateTarget);
      }
      resolve(true);
    } catch (e) {
      this.log(`copyMainFile执行异常=${e}`);
      resolve(false);
    }
  });
};
/* 删除压缩包 */
CheckApp.prototype.delFunc = function () {
  return new Promise((resolve) => {
    try {
      const mdlJson = this.modules;
      const mdlArr = Object.keys(mdlJson);
      // 删除依赖包
      if (mdlArr.length > 0) {
        for (const mdl of mdlArr) {
          const name = mdl.replace('/', '__');
          const mdlPath = path.join(this.resourcesDir, `${name}.zip`);
          let execCmd = `del/f/s/q ${mdlPath}`;
          if (process.platform === 'linux') {
            execCmd = `sudo rm -rf ${mdlPath}`;
          }
          this.log(`删除${name}.zip`);
          require('child_process').execSync(execCmd);
        }
      }
      const zipPath = path.join(this.resourcesDir, `${this.remoteVersion}.zip`);
      let execCmd = `del/f/s/q ${zipPath}`;
      if (process.platform === 'linux') {
        execCmd = `sudo rm -rf ${zipPath}`;
      }
      this.log(`删除${this.remoteVersion}.zip`);
      require('child_process').execSync(execCmd);

      resolve(true);
    } catch (e) {
      this.log(`delFunc执行异常=${e}`);
      resolve(false);
    }
  });
};
/*
 * 更新 app.json
 * name: 新版本目录 - 通常与新版本号相同
 * unlink: 上一版本目录 - 通常与旧版本号相同
 * origin：最新一次全量更新版本
 * oldVersion: 旧版本号
 * newVersion: 新版本号
 *  */
CheckApp.prototype.updateAppJson = function (appDir) {
  return new Promise((resolve) => {
    try {
      const jsonFile = path.join(this.resourcesDir, './app.json');
      if (!fs.existsSync(jsonFile)) {
        fs.writeFileSync(
          jsonFile,
          JSON.stringify({
            name: appDir,
            unlink: '',
            origin: app.getName(0),
            oldVersion: app.getName(0),
            newVersion: this.remoteVersion,
          })
        );
        resolve(true);
      } else {
        const json = fs.readFileSync(jsonFile);
        if (json) {
          const data = JSON.parse(json);
          if (data.name !== appDir) {
            fs.writeFileSync(
              jsonFile,
              JSON.stringify({
                name: appDir,
                unlink: data.name,
                origin: data.origin,
                oldVersion: app.getName(0),
                newVersion: this.remoteVersion,
              })
            );
          }
          resolve(true);
        } else {
          fs.writeFileSync(
            jsonFile,
            JSON.stringify({
              name: appDir,
              unlink: '',
              origin: app.getName(0),
              oldVersion: app.getName(0),
              newVersion: this.remoteVersion,
            })
          );
          resolve(true);
        }
      }
    } catch (e) {
      this.log(`updateAppJson执行异常=${e}`);
      resolve(false);
    }
  });
};
/* 更新 */
CheckApp.prototype.update = async function (newVersion) {
  try {
    this.log(`执行增量更新${newVersion}`);
    const down = await this.downLoadFunc();
    if (!down) return this.end();

    const appDir = await this.getAppDirName();
    if (!appDir) return this.end();

    const extract = await this.extractFunc(appDir);
    if (!extract) return this.end();

    const copy = await this.copyMainFile(appDir);
    if (!copy) return this.end();

    const upJson = await this.updateAppJson(appDir);
    if (!upJson) return this.end();

    const del = await this.delFunc();
    if (!del) return this.end();

    this.log(`增量更新完成,更新后版本${newVersion}`);
    this.end(newVersion);
  } catch (e) {
    this.log(`update执行异常=${e}`);
    this.end();
  }
};
CheckApp.prototype.extractAllToAsync = function (zipPath, targetPath) {
  return new Promise((resolve, reject) => {
    if (process.platform === 'linux' && process.arch === 'arm') {
      try {
        require('child_process').execSync(`unzip -o ${zipPath} -d ${targetPath}`);
        resolve(true);
      } catch (error) {
        reject(error);
      }
    } else {
      const AdmZip = require('adm-zip');
      const zip = new AdmZip(zipPath);
      zip.extractAllToAsync(targetPath, true, (err) => {
        if (err) reject(err);
        resolve(true);
      });
    }
  });
};
module.exports = (opts) => {
  const checkApp = new CheckApp(opts);
  checkApp.checkVersion().then((version) => {
    if (!version && checkApp.end) return checkApp.end();
    checkApp.start && checkApp.start(version);
    return checkApp.update(version);
  });
};
