import path from 'path';
import {Plugin} from '../schema/plugin';
import {parseYaml} from '../utils/yaml';
import config from '../config';
import {LATEST, UNKNOWN_VERSION} from '../utils/constant';
import VersionStyle from './version';
import fs from '../fs';
import {logger} from '../utils/logger';
import axios from "axios";

// TODO(blurooochen): 实现仓库和本地文件夹的安装升级逻辑
export enum PluginType {
  Unknown,
  // 来源于应用商店
  AppStore,
  // 来源于仓库
  GitRepo,
  // 来源于本地文件夹
  Dir
}

export class PluginInfo {
  // strict 是否采用严格模式，严格模式将要求包下面存在安装标志文件
  static async fromDir(dir: string, strict: boolean): Promise<PluginInfo | void> {
    if (!await isInstalled(dir, strict)) {
      return;
    }
    const pi = new PluginInfo(path.basename(dir));
    await pi.tryRecover()
    return pi;
  }

  // 插件所在路径，可能是 xx/feflow-plugin-xx@latest，指向真实版本
  path: string;
  // 插件地址
  repo: string | undefined;
  // 安装源地址，仅 pluginType 为 PluginType.Dir 时有效
  fromDir: string | undefined;
  // 插件全称，带 feflow-plugin- 前缀
  fullName: string = '';
  // 插件名称，简写，例如 epc
  name: string = '';
  // 实际版本，例如 v0.0.1
  realVersion: string | undefined;
  // 安装版本，例如 latest
  installVersion: string;
  // 包类型
  pluginType: PluginType = PluginType.Unknown;

  private protocol: Plugin | undefined;

  constructor(name: string, version?: string) {
    let pluginName;
    let pluginVersion;
    if (!version) {
      [pluginName, pluginVersion] = name.split('@');
    } else {
      [pluginName, pluginVersion] = [name, version];
    }
    this.setPluginFromInfo(pluginName);
    if (!pluginVersion || pluginVersion === LATEST) {
      this.installVersion = LATEST;
    } else {
      // Note: 只要符合版本范围规则即可，也即允许安装 @^0.0.x @~0.0.x 等形式的版本
      if (!VersionStyle.checkRange(pluginVersion)) {
        throw `invalid version: ${pluginVersion}`;
      }
      this.installVersion = VersionStyle.valid(pluginVersion);
    }
    this.path = path.join(config.pluginRootDir, `${this.fullName}@${this.installVersion}`);
  }

  setRealVersion(realVersion: string) {
    this.realVersion = realVersion;
  }

  // 获取已安装的具体版本
  public async getInstalledVersion(): Promise<string> {
    if (this.realVersion) {
      return this.realVersion;
    }
    await this.tryRecover();
    return this.realVersion || UNKNOWN_VERSION;
  }

  // 尝试从版本记录里恢复信息
  public async tryRecover() {
    try {
      const df = doneFile(this.path);
      const data = await fs.readFile(df);
      const plugin = JSON.parse(data.toString());
      const protocol = plugin.protocol;
      delete plugin.protocol;
      Object.assign(this, plugin);
      this.protocol = new Plugin(this.path, protocol);
    } catch {}
  }

  getRealPath(realVersion?: string): string {
    return path.join(config.pluginRootDir, `${this.fullName}@${realVersion || this.realVersion}`);
  }

  async getProtocol(version?: string): Promise<Plugin> {
    if (!this.protocol) {
      const protocolFile = await this.getProtocolFile(version);
      const protocol = await parseYaml(protocolFile);
      this.protocol = new Plugin(this.path, protocol);
    }
    return this.protocol;
  }

  // 获取协议文件
  // 通常来说只需要获取真实版本下的协议文件
  public async getProtocolFile(version?: string) {
    let protocolFile = path.join(this.getRealPath(version), config.protocolFileName);
    try {
      await fs.access(protocolFile);
    } catch (e) {
      logger.debug(`真实路径不存在：${protocolFile}，将尝试转换为指向路径`);
      protocolFile = path.join(this.path, config.protocolFileName);
      await fs.access(protocolFile);
    }
    return protocolFile;
  }

  public async getRepoUrl(): Promise<string> {
    if (this.repo) {
      return this.repo;
    }
    await axios.get(`${config.storeUrl}apply/getlist?name=${this.fullName}`, { timeout: 2000 })
      .then((response: any) => {
        // 属于前端包时，直接返回空
        if (response?.data?.data?.[0]?.tnpm) {
          return '';
        }
        this.repo = response?.data?.data?.[0]?.repo;
      }).catch(err => {
        logger.warn(`request fef app store failed, err: ${err}`);
        this.repo = `https://git.woa.com/cli-market/${this.name}.git`;
      });
    return this.repo || '';
  }

  public async isInstalled(): Promise<boolean> {
    return isInstalled(this.path, true);
  }

  public isRangeVersion(): boolean {
    return VersionStyle.isRange(this.installVersion);
  }

  public async done(): Promise<void> {
    const df = path.join(this.getRealPath(), config.fefDoneFile);
    await fs.writeFile(df, JSON.stringify(this, null, '\t'), {
      flag: 'w',
      encoding: 'utf8',
    });
    if (!this.isRangeVersion()) {
      return;
    }
    // 用户安装的是非特定版本时，应该进行软链
    await fs.link(this.getRealPath(), this.path);
  }

  // 获取插件类型
  public getType(): string {
    switch (this.pluginType) {
      case PluginType.Dir:
        return 'dir';
      case PluginType.GitRepo:
        return 'git';
      default:
        return 'app';
    }
  }

  private setPluginFromInfo(pluginName: string) {
    if (!pluginName.startsWith(config.pluginPrefix)) {
      if (isRepo(pluginName)) {
        this.fullName = getFullNameFromRepo(pluginName);
        this.pluginType = PluginType.GitRepo;
        this.repo = this.name = "https://" + getNameFromRepo(pluginName);
        return;
      }
      if (isDir(pluginName)) {
        this.fullName = getFullNameFromDir(pluginName);
        this.pluginType = PluginType.Dir;
        this.fromDir = this.name = pluginName;
        return;
      }
      this.fullName = config.pluginPrefix + pluginName;
      this.name = pluginName;
      this.pluginType = PluginType.AppStore;
      return;
    }
    this.fullName = pluginName;
    if (pluginName.startsWith(config.pluginDirPrefix)) {
      this.pluginType = PluginType.Dir;
      this.name = this.fromDir = decodeURIComponent(pluginName.substring(config.pluginDirPrefix.length));
      return;
    }
    if (pluginName.startsWith(config.pluginRepoPrefix)) {
      this.pluginType = PluginType.GitRepo;
      this.name = this.repo = "https://" + decodeURIComponent(pluginName.substring(config.pluginRepoPrefix.length));
      return;
    }
    this.pluginType = PluginType.AppStore;
    this.name = pluginName.substring(config.pluginPrefix.length);
  }
}

function isDir(plugin: string): boolean {
  try {
    return fs.statSync(plugin).isDirectory();
  } catch (e) {
    return false;
  }
}

function isRepo(plugin: string): boolean {
  return plugin.endsWith(".git")
}

function doneFile(dir: string): string {
  return path.join(dir, config.fefDoneFile);
}

async function isInstalled(dir: string, strict: boolean = true): Promise<boolean> {
  const df = strict ? doneFile(dir) : dir;
  try {
    await fs.access(df, fs.constants.F_OK);
    return true;
  } catch (e) {
    return false;
  }
}

// git@github.com:tencent/feflow.git
// or http[s]://github.com/tencent/feflow.git or http[s]://user:pwd@github.com/tencent/feflow.git
// to
// encodeURIComponent("github.com/tencent/feflow.git")
function getFullNameFromRepo(repo: string): string {
  return encodeURIComponent(config.pluginRepoPrefix + getNameFromRepo(repo));
}

function getNameFromRepo(repo: string): string {
  const ret = /^((http:\/\/|https:\/\/)(.*?@)?|git@)/.exec(repo);
  if (Array.isArray(ret) && ret.length > 0) {
    repo = repo.substring(ret[0].length);
  }
  return repo.split(':').join('/');
}

function getFullNameFromDir(dir: string): string {
  return encodeURIComponent(config.pluginDirPrefix + dir.trim());
}
