import { Certd } from '@certd/certd'
import { pluginRegistry, util } from '@certd/api'
import _ from 'lodash-es'
import dayjs from 'dayjs'
import { Trace } from './trace.js'
const logger = util.logger

function createDefaultOptions () {
  return {
    args: {
      forceCert: false,
      forceDeploy: true,
      forceRedeploy: false,
      doNotThrowError: false // 部署流程执行有错误时，不抛异常，此时整个任务执行完毕后，可以返回结果，你可以在返回结果中处理
    }
  }
}
export class Executor {
  constructor () {
    this.trace = new Trace()
  }

  async run (options) {
    logger.info('------------------- Cert-D ---------------------')
    try {
      this.transfer(options)
      options = _.merge(createDefaultOptions(), options)
      return await this.doRun(options)
    } catch (e) {
      logger.error('任务执行出错', e)
      throw e
    }
  }

  transfer (options) {
    const providers = options.accessProviders
    if (_.isArray(providers)) {
      const map = {}
      for (const provider of providers) {
        if (provider.key) {
          map[provider.key] = provider
        }
      }
      options.accessProviders = map
    }
  }

  async doRun (options) {
    // 申请证书
    logger.info('任务开始')
    const certd = new Certd(options)
    const cert = await this.runCertd(certd)
    if (cert == null) {
      throw new Error('申请证书失败')
    }
    logger.info('证书保存路径:', cert.certDir)

    logger.info('----------------------')
    if (!cert.isNew) {
      // 如果没有更新
      if (options.args.forceRedeploy) {
        // 强制重新部署,清空保存的状态
        await certd.certStore.setCurrentFile('context.json', '{}')
      } else if (!options.args.forceDeploy) {
        // 且不需要强制deploy
        logger.info('证书无更新，无需重新部署')
        logger.info('任务完成')
        return { cert }
      }
    }
    // 读取上次执行进度
    let context = {}
    const contextJson = await certd.certStore.getCurrentFile('context.json')
    if (contextJson) {
      context = JSON.parse(contextJson)
    }

    context.certIsNew = !!cert.isNew

    const trace = new Trace(context)
    const resultTrace = trace.getInstance({ type: 'result' })
    // 运行部署任务
    try {
      await this.runDeploys({ options, cert, context, trace })
    } finally {
      await certd.certStore.setCurrentFile('context.json', JSON.stringify(context))
    }
    logger.info('任务完成')
    trace.print()
    const result = resultTrace.get({ })
    if (result) {
      if (result.status === 'error' && options.args.doNotThrowError === false) {
        throw new Error(result.remark)
      }
    }
    return {
      cert,
      context,
      result
    }
  }

  async runCertd (certd) {
    logger.info(`证书任务 ${JSON.stringify(certd.options.cert.domains)} 开始`)
    const cert = await certd.certApply()
    logger.info(`证书任务 ${JSON.stringify(certd.options.cert.domains)} 完成`)
    return cert
  }

  async runDeploys ({ options, cert, context, trace }) {
    if (cert == null) {
      const certd = new Certd(options)
      cert = await certd.readCurrentCert()
    }
    logger.info('部署任务开始')
    for (const deploy of options.deploy) {
      const deployName = deploy.deployName
      logger.info(`------------【${deployName}】-----------`)

      const deployTrace = trace.getInstance({ type: 'deploy', deployName })
      if (deploy.disabled === true) {
        logger.info('此流程已被禁用，跳过')
        logger.info('')
        deployTrace.set({ value: { current: 'skip', status: 'disabled', remark: '流程禁用' } })
        deployTrace.set({ tasks: null })
        continue
      }
      try {
        for (const task of deploy.tasks) {
          if (context[deployName] == null) {
            context[deployName] = {}
          }
          const taskContext = context[deployName]
          // 开始执行任务列表
          await this.runTask({ options, cert, task, context: taskContext, deploy, trace })
        }

        deployTrace.set({ value: { status: 'success', remark: '执行成功' } })
        trace.set({ type: 'result', value: { status: 'success', remark: '执行成功' } })
      } catch (e) {
        deployTrace.set({ value: { status: 'error', remark: '执行失败：' + e.message } })
        trace.set({ type: 'result', value: { status: 'error', remark: deployName + '执行失败：' + e.message } })
        logger.error('流程执行失败', e)
      }

      logger.info('')
    }
  }

  async runTask ({ options, task, cert, context, deploy, trace }) {
    const taskType = task.type
    const Plugin = pluginRegistry.get(taskType)
    const deployName = deploy.deployName
    const taskName = task.taskName
    if (Plugin == null) {
      throw new Error(`插件：${taskType}还未安装`)
    }

    let instance = Plugin
    if (Plugin instanceof Function) {
      instance = new Plugin({ accessProviders: options.accessProviders })
    }
    const taskTrace = trace.getInstance({ type: 'deploy', deployName, taskName })
    const traceStatus = taskTrace.get({})
    if (traceStatus && traceStatus.status === 'success' && !options.args.forceRedeploy) {
      logger.info(`----【${taskName}】已经执行完成，跳过此任务`)
      taskTrace.set({ value: { current: 'skip', status: 'success', remark: '已执行成功过，本次跳过' } })
      return
    }
    logger.info(`----【${taskName}】开始执行`)
    try {
      // 执行任务
      await instance.execute({ cert, props: task.props, context })
      taskTrace.set({ value: { current: 'success', status: 'success', remark: '执行成功', time: dayjs().format() } })
    } catch (e) {
      taskTrace.set({ value: { current: 'error', status: 'error', remark: e.message, time: dayjs().format() } })
      throw e
    }
    logger.info(`----任务【${taskName}】执行完成`)
    logger.info('')
  }
}
