import {
  BaseService,
  CoolCommException,
  CoolEventManager,
} from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Equal, In, Not, Repository } from 'typeorm';
import { PluginInfoEntity } from '../entity/info';
import {
  App,
  Config,
  ILogger,
  IMidwayApplication,
  IMidwayContext,
  Inject,
  InjectClient,
  Logger,
  Provide,
} from '@midwayjs/core';
import * as _ from 'lodash';
import { PluginInfo } from '../interface';
import { PluginCenterService } from './center';
import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager';
import {
  GLOBAL_EVENT_PLUGIN_INIT,
  GLOBAL_EVENT_PLUGIN_REMOVE,
} from '../event/init';
import { PluginMap, AnyString } from '../../../../typings/plugin';
import { PluginTypesService } from './types';
import * as path from 'path';
import * as fs from 'fs';
import { pPluginPath } from '../../../comm/path';
/**
 * 插件信息
 */
@Provide()
export class PluginService extends BaseService {
  @InjectEntityModel(PluginInfoEntity)
  pluginInfoEntity: Repository<PluginInfoEntity>;

  @Inject()
  ctx: IMidwayContext;

  @App()
  app: IMidwayApplication;

  @Inject()
  pluginCenterService: PluginCenterService;

  @Config('module.plugin.hooks')
  hooksConfig;

  @InjectClient(CachingFactory, 'default')
  midwayCache: MidwayCache;

  @Inject()
  coolEventManager: CoolEventManager;

  @Inject()
  pluginTypesService: PluginTypesService;

  @Logger()
  logger: ILogger;

  /**
   * 新增或更新
   * @param param
   * @param type
   */
  async addOrUpdate(param: any, type?: 'add' | 'update') {
    await super.addOrUpdate(param, type);
    const info = await this.pluginInfoEntity
      .createQueryBuilder('a')
      .select(['a.id', 'a.keyName', 'a.status', 'a.hook'])
      .where({
        id: Equal(param.id),
      })
      .getOne();
    if (info.status == 1) {
      await this.reInit(info.keyName);
    } else {
      await this.remove(info.keyName, !!info.hook);
    }
  }

  /**
   * 重新初始化插件
   */
  async reInit(keyName: string) {
    // 多进程发送全局事件，pm2下生效，本地开发则通过普通事件
    this.coolEventManager.globalEmit(GLOBAL_EVENT_PLUGIN_INIT, false, keyName);
  }

  /**
   * 移除插件
   * @param keyName
   * @param isHook
   */
  async remove(keyName: string, isHook = false) {
    // 多进程发送全局事件，pm2下生效
    this.coolEventManager.globalEmit(
      GLOBAL_EVENT_PLUGIN_REMOVE,
      false,
      keyName,
      isHook
    );
    this.pluginTypesService.deleteDtsFile(keyName);
  }

  /**
   * 删除不经过回收站
   * @param ids
   */
  async delete(ids: any) {
    const list = await this.pluginInfoEntity.findBy({ id: In(ids) });
    for (const item of list) {
      await this.remove(item.keyName, !!item.hook);
      // 删除文件
      await this.deleteData(item.keyName);
    }
    await this.pluginInfoEntity.delete(ids);
  }

  /**
   * 更新
   * @param param
   */
  async update(param: any) {
    const old = await this.pluginInfoEntity.findOne({
      where: { id: param.id },
      select: ['id', 'status', 'hook'],
    });
    // 启用插件，禁用同名插件
    if (old.hook && param.status == 1 && old.status != param.status) {
      await this.pluginInfoEntity.update(
        { hook: old.hook, status: 1, id: Not(old.id) },
        { status: 0 }
      );
    }
    await super.update(param);
  }

  /**
   * 获得插件配置
   * @param key
   */
  async getConfig(key: string) {
    return this.pluginCenterService.pluginInfos.get(key)?.config;
  }

  /**
   * 调用插件
   * @param key 插件key
   * @param method 方法
   * @param params 参数
   * @returns
   */
  async invoke<K extends keyof PluginMap>(
    key: K | AnyString,
    method: string,
    ...params
  ) {
    // 实例
    const instance: any = await this.getInstance(key);
    return await instance[method](...params);
  }

  /**
   * 获得插件实例
   * @param key
   * @returns
   */
  async getInstance<K extends keyof PluginMap>(
    key: K | AnyString
  ): Promise<K extends keyof PluginMap ? PluginMap[K] : any> {
    const check = await this.checkStatus(key);
    if (!check) throw new CoolCommException(`插件[${key}]不存在或已禁用`);
    let instance;
    const pluginInfo = this.pluginCenterService.pluginInfos.get(key);
    if (pluginInfo.singleton) {
      instance = this.pluginCenterService.plugins.get(key);
    } else {
      instance = new (await this.pluginCenterService.plugins.get(key))();
      await instance.init(pluginInfo, this.ctx, this.app, {
        cache: this.midwayCache,
        pluginService: this,
      });
    }
    return instance;
  }

  /**
   * 检查状态
   * @param key
   */
  async checkStatus(key: string) {
    if (Object.keys(this.hooksConfig).includes(key)) {
      return true;
    }
    const info = await this.pluginInfoEntity
      .createQueryBuilder('a')
      .select(['a.id', 'a.status'])
      .where({ status: 1, keyName: Equal(key) })
      .getOne();

    return !!info;
  }

  /**
   * 检查
   * @param filePath
   */
  async check(filePath: string) {
    let data;
    try {
      data = await this.data(filePath);
    } catch (e) {
      return {
        type: 0,
        message: `插件信息不完整，请检查${data?.errorData || ''}`,
      };
    }
    const check = await this.pluginInfoEntity.findOne({
      where: { keyName: Equal(data.pluginJson.key) },
      select: ['id', 'hook', 'status'],
    });
    if (check && !check.hook) {
      return {
        type: 1,
        message: '插件已存在，继续安装将覆盖',
      };
    }
    if (check && check.hook && check.status == 1) {
      return {
        type: 2,
        message:
          '已存在同名Hook插件，你可以继续安装，但是多个相同的Hook插件只能同时开启一个',
      };
    }
    return {
      type: 3,
      message: '检查通过',
    };
  }

  /**
   * 获得插件数据
   * @param filePath
   */
  async data(filePath: string): Promise<{
    pluginJson: any;
    readme: string;
    logo: string;
    content: string;
    tsContent: string;
    errorData: string;
  }> {
    const AdmZip = require('adm-zip');
    const zip = new AdmZip(filePath);
    const files = zip.getEntries();
    let errorData;
    let pluginJson: PluginInfo,
      readme: string,
      logo: string,
      content: string,
      tsContent: string;

    try {
      // 通用方法获取文件内容
      const getFileContent = (
        entryName: string,
        encoding: 'utf-8' | 'base64' = 'utf-8'
      ) => {
        const file = _.find(files, { entryName });
        if (!file) {
          throw new Error(`File ${entryName} not found`);
        }
        return file?.getData()?.toString(encoding);
      };

      errorData = 'plugin.json';
      pluginJson = JSON.parse(getFileContent('plugin.json'));

      errorData = 'readme';
      readme = getFileContent(pluginJson.readme);

      errorData = 'logo';
      logo = getFileContent(pluginJson.logo, 'base64');

      errorData = 'content';
      content = getFileContent('src/index.js');

      tsContent = getFileContent('source/index.ts');
    } catch (e) {
      throw new CoolCommException('插件信息不完整');
    }
    return {
      pluginJson,
      readme,
      logo,
      content,
      tsContent,
      errorData,
    };
  }

  /**
   * 安装插件
   * @param file 文件
   * @param force 是否强制安装
   */
  async install(filePath: string, force = false) {
    const forceBool = typeof force === 'string' ? force === 'true' : force;
    const checkResult = await this.check(filePath);
    if (checkResult.type != 3 && !forceBool) {
      return checkResult;
    }
    const { pluginJson, readme, logo, content, tsContent } = await this.data(
      filePath
    );
    if (pluginJson.key == 'plugin') {
      throw new CoolCommException('插件key不能为plugin，请更换其他key');
    }
    const check = await this.pluginInfoEntity.findOne({
      where: { keyName: Equal(pluginJson.key) },
      select: ['id', 'status', 'config'],
    });
    const data = {
      name: pluginJson.name,
      keyName: pluginJson.key,
      version: pluginJson.version,
      author: pluginJson.author,
      hook: pluginJson.hook,
      readme,
      logo,
      content: {
        type: 'comm',
        data: content,
      },
      tsContent: {
        type: 'ts',
        data: tsContent,
      },
      description: pluginJson.description,
      pluginJson,
      config: pluginJson.config,
      status: 1,
    } as PluginInfoEntity;
    // 存在同名插件，更新，保留配置
    if (check) {
      await this.pluginInfoEntity.update(check.id, {
        ...data,
        status: check.status,
        config: {
          ...pluginJson.config,
          ...check.config,
        },
      });
    } else {
      // 全新安装
      await this.pluginInfoEntity.insert(data);
    }
    // 保存插件内容
    await this.saveData(
      {
        content: {
          type: 'comm',
          data: content,
        },
        tsContent: {
          type: 'ts',
          data: tsContent,
        },
      },
      pluginJson.key
    );
    this.pluginTypesService.generateDtsFile(pluginJson.key, tsContent);
    // 初始化插件
    await this.reInit(pluginJson.key);
  }

  /**
   * 将插件内容保存到文件
   * @param content 内容
   * @param keyName 插件key
   */
  async saveData(
    data: {
      content: {
        type: 'comm' | 'module';
        data: string;
      };
      tsContent: {
        type: 'ts';
        data: string;
      };
    },
    keyName: string
  ) {
    const filePath = this.pluginPath(keyName);
    // 确保目录存在
    const dir = path.dirname(filePath);
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir, { recursive: true });
    }
    // 写入文件，如果存在则覆盖
    fs.writeFileSync(filePath, JSON.stringify(data, null, 0), { flag: 'w' });
  }

  /**
   * 获得插件数据
   * @param keyName
   * @returns
   */
  async getData(keyName: string): Promise<{
    content: {
      type: 'comm' | 'module';
      data: string;
    };
    tsContent: {
      type: 'ts';
      data: string;
    };
  }> {
    const filePath = this.pluginPath(keyName);
    if (!fs.existsSync(filePath)) {
      // 尝试从数据库中获取
      const info = await this.pluginInfoEntity.findOne({
        where: { keyName: Equal(keyName) },
        select: ['content', 'tsContent'],
      });
      if (info) {
        // 保存插件到文件
        this.saveData(
          {
            content: info.content,
            tsContent: info.tsContent,
          },
          keyName
        );
        return {
          content: info.content,
          tsContent: info.tsContent,
        };
      } else {
        this.logger.warn(
          `插件[${keyName}]文件不存在，请卸载后重新安装: ${filePath}`
        );
        return;
      }
    }
    return JSON.parse(await fs.promises.readFile(filePath, 'utf-8'));
  }

  /**
   * 删除插件
   * @param keyName
   */
  async deleteData(keyName: string) {
    const filePath = this.pluginPath(keyName);
    if (fs.existsSync(filePath)) {
      fs.unlinkSync(filePath);
    }
  }

  /**
   * 获得插件路径
   * @param keyName
   * @returns
   */
  pluginPath(keyName: string) {
    return path.join(pPluginPath(), `${keyName}`);
  }
}
