import { ConfigService } from '@nestjs/config';
import { Eureka, EurekaClient } from 'eureka-js-client';
import { resolve } from 'path';
import { notNilEmpty } from 'qx-util';
import { GatewayRegisterCenterService } from '../../interface';
import { logger } from '../../utils';

/**
 * eureka 注册中心服务
 *
 * @author chitanda
 * @date 2022-04-02 10:04:41
 * @export
 * @class EurekaService
 * @implements {GatewayRegisterCenterService}
 */
export class EurekaService implements GatewayRegisterCenterService {
  private static instance: EurekaService;

  /**
   * 重新获取实例信息的心跳时间
   *
   * @author chitanda
   * @date 2022-04-02 10:04:19
   * @protected
   */
  protected timeout = 1000 * 10;

  /**
   * 定时器标识
   *
   * @author chitanda
   * @date 2022-04-02 11:04:51
   * @protected
   */
  protected timer?: NodeJS.Timer;

  /**
   * 服务对应转发地址
   *
   * @author chitanda
   * @date 2022-04-02 10:04:30
   * @protected
   * @type {Map<string, string[]>}
   */
  protected address: Map<string, string[]> = new Map();

  /**
   * eureka客户端实例
   *
   * @author chitanda
   * @date 2022-04-02 10:04:48
   * @protected
   * @type {Eureka}
   */
  protected readonly client: Eureka;

  constructor(public configService: ConfigService) {
    if (EurekaService.instance) {
      return EurekaService.instance;
    }
    EurekaService.instance = this;
    this.client = new Eureka({
      logger,
      filename: 'eureka-client',
      cwd: resolve(__dirname, '../../../../configs'),
    });
  }

  /**
   * 根据服务名称获取服务标识
   *
   * @author chitanda
   * @date 2022-04-02 10:04:08
   * @param {string} serviceName
   * @return {*}  {string}
   */
  getServiceId(serviceName: string): string {
    return this.configService.get(`gateway.${serviceName}.serviceId`);
  }

  /**
   * 根据服务名称获取对应地址
   *
   * @author chitanda
   * @date 2022-04-02 10:04:12
   * @param {string} serviceName
   * @return {*}  {string[]}
   */
  getAddress(serviceName: string): string[] {
    return this.address.get(serviceName) || [];
  }

  /**
   * 启动 eureka 客户端
   *
   * @author chitanda
   * @date 2022-04-02 09:04:14
   * @return {*}  {Promise<void>}
   */
  async start(): Promise<void> {
    return new Promise<void>(resolve => {
      this.client.start((err: Error, ...rest: any[]) => {
        if (err) {
          throw new Error(`eureka client start error: ${err.message}`);
        }
        if (this.timer) {
          clearInterval(this.timer);
          this.timer = undefined;
        }
        this.timer = setInterval(() => {
          this.subAllInstances();
        }, this.timeout);
        this.subAllInstances();
        resolve(...rest);
      });
    });
  }

  /**
   * 订阅所有实例
   *
   * @author chitanda
   * @date 2022-04-02 10:04:40
   * @protected
   */
  protected subAllInstances() {
    const NacosGateway = this.configService.get('gateway');
    const allServiceId: Set<string> = new Set();
    for (const key in NacosGateway) {
      const cfg = NacosGateway[key];
      allServiceId.add(cfg.serviceId);
    }
    allServiceId.forEach(serviceId => {
      const configs = this.client.getInstancesByAppId(serviceId);
      this.calcAddress(serviceId, configs);
    });
  }

  /**
   * 计算
   *
   * @author chitanda
   * @date 2021-08-12 11:08:00
   * @protected
   * @param {string} tag
   * @param {EurekaClient.EurekaInstanceConfig[]} configs
   */
  protected calcAddress(tag: string, configs: EurekaClient.EurekaInstanceConfig[]): void {
    const arr: string[] = [];
    const protocol = this.configService.get('app.gatewayProtocol');
    if (notNilEmpty(configs)) {
      configs.forEach(config => {
        if (config) {
          if (config.port && config.port['@enabled']) {
            arr.push(`${protocol}://${config.ipAddr}:${config.port['$']}`);
          }
        } else {
          arr.push(`${protocol}://${config.ipAddr}`);
        }
      });
    }
    if (notNilEmpty(arr)) {
      this.address.set(tag, arr);
    }
  }

  /**
   * 获取实例
   *
   * @author chitanda
   * @date 2022-04-02 09:04:31
   * @static
   * @return {*}  {EurekaService}
   */
  public static getInstance(): EurekaService {
    return this.instance;
  }
}
