/** @format */

import { Eureka, EurekaClient } from 'eureka-js-client'
import axios from 'axios'
import { checkType, jj, random } from './utils'
import * as address from 'address'
const Netmask = require('netmask').Netmask

const ip = address.ip() || '127.0.0.1'

export interface IEurekaObjs {
  [name: string]: string[]
}

export default class Pool {
  private eurekaObjs: IEurekaObjs = {}
  private instances: EurekaClient.EurekaInstanceConfig[] = []
  constructor(eureka: Eureka, services: string[]) {
    setTimeout(() => {
      this.getEurekas(eureka, services)
    }, 3000)

    setInterval(() => {
      this.filterPool()
    }, 4000)
  }

  /**
   * 遍历eureka
   * @param {Eureka} eureka  Eureka
   * @param {string[]} services 服务名
   */
  private async getEurekas(eureka: Eureka, services: string[]) {
    for (let i = 0; i < services.length; i++) {
      const name = services[i]
      const instances = eureka.getInstancesByAppId(name)
      const tmpList = instances.map(v => {
        return `http://${v.hostName}:${(<{ $: number }>v.port).$}`
      })
      this.eurekaObjs[name] = tmpList
      this.instances.push(...instances)
    }
    // 每30秒刷新eurekaObjs列表
    setTimeout(() => {
      this.getEurekas(eureka, services)
    }, 30000)
  }
  private async filterPool() {
    const { eurekaObjs } = this
    for (const i in eurekaObjs) {
      const obj = eurekaObjs[i]
      for (let j = 0; j < obj.length; j++) {
        try {
          let params = ''
          if (i === 'duiba-manager-web') {
            params = 'newmanager/'
          }
          const { data } = await axios.get(`${obj[j]}/${params}monitor/check`)
          if (checkType(data, 'String')) {
            if (data.toLowerCase() !== 'ok') {
              eurekaObjs[i].splice(j, 1)
            }
          } else {
            eurekaObjs[i].splice(j, 1)
          }
        } catch (e) {
          eurekaObjs[i].splice(j, 1)
        }
      }
      eurekaObjs[i] = eurekaObjs[i].filter(v => v !== undefined)
    }
  }

  /**
   * 获取所有eureka
   */
  getAllEurekas(): EurekaClient.EurekaInstanceConfig[] {
    return this.instances
  }

  /**
   * 获取所有实例下的host
   */
  getAllHostName() {
    return this.eurekaObjs
  }

  private getRandomHost(host: string[]) {
    const len = host.length
    const num = random(0, len - 1)
    return host[num] || ''
  }

  getHost(name: string | number, localIp: string, filterGroup?: string) {
    const { eurekaObjs, instances } = this
    const pool = eurekaObjs[name]
    if (!pool) {
      console.error('请先注册service')
      process.exit(1)
    }

    const { NODE_ENV } = process.env

    if (NODE_ENV === 'prod') {
      return this.filterHybridCloud(instances, pool)
    }

    // 优先走本地
    let hasLocalIp = false
    let clientIp = ''
    for (let i = 0; i < pool.length; i++) {
      if (pool[i].includes(localIp)) {
        hasLocalIp = true
        clientIp = pool[i]
      }
    }

    if (hasLocalIp) {
      return clientIp
    }

    /** 最优先匹配具体标记的，即多环境 */
    let highestPriorityList = [] // 最高优先级  匹配到具体的metadata
    let havePriorityList = [] // 优先转发目标   除具体metadata之外的metadata

    /** 没有标记则默认转发到公共服务 */
    let thirdPriorityList = [] // 第三优先级   runInSingleJarMode=true的是公共服务

    /** 个人服务 */
    const noGroupService = [] // 没有分组标记     没有任何标记

    for (const i in instances) {
      const ins = instances[i]
      const host = `http://${ins.hostName}:${(<{ $: number }>ins.port).$}`
      const { metadata } = ins
      const group = (metadata as Record<string, string>)['duiba.service.group.key']
      const runInSingleJarMode = (metadata as Record<string, string>).runInSingleJarMode === 'true'

      if (filterGroup && group) {
        // 如果存在_duibaServiceGroupKey 且存在多场景
        if (group === filterGroup) {
          // 完全匹配metadata
          highestPriorityList.push(host)
        } else if (group) {
          // 匹配不到，多场景为最低优先级
          noGroupService.push(host)
        } else {
          if (runInSingleJarMode) {
            havePriorityList.push(host)
          } else {
            thirdPriorityList.push(host)
          }
        }
      } else {
        if (!group) {
          if (runInSingleJarMode) {
            havePriorityList.push(host)
          } else {
            thirdPriorityList.push(host)
          }
        } else {
          noGroupService.push(host)
        }
      }
    }
    highestPriorityList = jj(highestPriorityList, pool)
    havePriorityList = jj(havePriorityList, pool)
    thirdPriorityList = jj(thirdPriorityList, pool)

    let tmpPool: string[] = []

    if (highestPriorityList.length > 0) {
      // 如果完全匹配
      return this.getRandomHost(highestPriorityList)
    } else if (havePriorityList.length > 0) {
      // 存在优先级高的
      return this.getRandomHost(havePriorityList)
    } else if (thirdPriorityList.length > 0) {
      return this.getRandomHost(thirdPriorityList)
    } else {
      // 同网段的优先
      // @ts-ignore
      const { cidr } = address.interface()
      const block = new Netmask(cidr)
      pool.forEach(v => {
        if (block.contains(v.split('http://')[1])) {
          tmpPool.push(v)
        }
      })

      if (tmpPool.length === 0) {
        // 如果不存在同网段，随便
        tmpPool = pool
      }
      return this.getRandomHost(tmpPool)
    }
  }

  /**
   * 筛选混合云符合条件IP
   * http://cf.dui88.com/pages/viewpage.action?pageId=38788463
   * @param {EurekaClient.EurekaInstanceConfig[]} instances 服务实例
   * @param {*} pool  ip池
   * @returns
   * @memberof Pool
   */
  filterHybridCloud(instances: EurekaClient.EurekaInstanceConfig[], pool: any) {
    let currentApplicationZone = 'defaultZone'

    let aliyunCloud = []
    let huaweiCloud = []

    for (let i = 0; i < instances.length; i++) {
      const ins = instances[i]
      const { hostName, metadata } = ins
      const host = `http://${hostName}:${(<{ $: number }>ins.port).$}`

      const { zone } = metadata as Record<string, string>

      if (hostName === ip) {
        currentApplicationZone = zone === 'huawei' ? 'huawei' : 'defaultZone'
      }

      // 阿里云机器环注册的metadata为zone=defaultZone，华为云注册的metadata为huawei (假如获取ZONE为空，则视为defaultZone，注册到eureka的zone值也为defaultZone)
      if (zone === 'huawei') {
        huaweiCloud.push(host)
      } else {
        aliyunCloud.push(host)
      }
    }

    aliyunCloud = jj(aliyunCloud, pool)
    huaweiCloud = jj(huaweiCloud, pool)

    const aliyunCloudLength = aliyunCloud.length
    const huaweiCloudLength = huaweiCloud.length

    // 如果任一机房没有实例，则去另一个机房随机获取实例
    if (aliyunCloudLength === 0) {
      return this.getRandomHost(huaweiCloud)
    } else if (huaweiCloudLength === 0) {
      return this.getRandomHost(aliyunCloud)
    }

    const [min, max] = [aliyunCloudLength, huaweiCloudLength].sort((a, b) => a - b)

    // 判断服务在两个机房的分布是不是小于3，小于3做同机房优先调用，否则随机调用
    if (max / min < 3) {
      if (currentApplicationZone === 'huawei') {
        return this.getRandomHost(huaweiCloud)
      } else {
        return this.getRandomHost(aliyunCloud)
      }
    } else {
      return this.getRandomHost([...aliyunCloud, ...huaweiCloud])
    }
  }
}
