import groovy.json.JsonSlurper

pipeline {
  agent any

  options {
    skipDefaultCheckout()
  }

  parameters {
    string(name: 'SHARED_LIBRARIES_VERSION', defaultValue: 'master', description: 'The version of the Jenkins shared libraries to use. Can be a branch, tag, Git revision or PR ref (e.g. pull/PR_NUMBER/merge).')
    booleanParam(name: 'REFRESH_ALL', defaultValue: false, description: 'If set, all software catalog services and libraries are refreshed.')
    string(name: 'ENTITIES_TO_REFRESH', defaultValue: '', description: 'Comma-separated list of entities to refresh. Ignored if REFRESH_ALL is set.')
    choice(name: 'ENTITY_KIND', choices: ['service','library'], description: 'Kind of software catalog entities to refresh. Ignored if REFRESH_ALL is set.')
    string(name: 'MAX_PARALLEL_EXECUTIONS', defaultValue: '10', description: 'The number of allowed parallel executions.')
  }

  stages{
    stage('Load Shared Libraries') {
      steps {
          library "jenkins-global-libraries@${params.SHARED_LIBRARIES_VERSION}"
      }
    }
    stage("Run Software Catalog refresh") {
      steps {
        withEnv(["AWS_PROFILE="]) {
          withAWS(roleAccount: '086679231553', role: 'shared-datadog-software-catalog-role', roleSessionName: 'datadog-software-catalog-refresh', useNode: true) {
            withEcr(registries: [[accountId: '086679231553', region: 'us-east-1']]) {
              script {
                entities = getEntitiesToRefresh(params.REFRESH_ALL, params.ENTITIES_TO_REFRESH, params.ENTITY_KIND)


                def batches = []
                def results = [:]
                def batch_size = params.MAX_PARALLEL_EXECUTIONS.toInteger()

                entities.eachWithIndex { entity, idx ->
                  def jobName = "run-${idx}-${entity.metadata.name}"
                  batch_id = idx.intdiv(batch_size)
                  if (idx % batch_size == 0) {
                    batches[batch_id] = [:]
                  }
                  def paramsList = [
                    string(name: 'NAME', value: entity.metadata.name),
                    string(name: 'REPOSITORY_URL', value: entity.extensions['sonymusic-pde.com/metadata-source']['repositoryURL']),
                    string(name: 'PIPELINE_URL', value: entity.extensions['sonymusic-pde.com/metadata-source']['pipelineURL']),
                    string(name: 'REVISION', value: entity.extensions['sonymusic-pde.com/metadata-source']['revision']),
                    string(name: 'SERVICE_PATH', value: entity.extensions['sonymusic-pde.com/metadata-source']['servicePath']),
                    string(name: 'SERVICE_DEFINITION_FILE_PATH', value: entity.extensions['sonymusic-pde.com/metadata-source']?.get('serviceDefinitionFilePath') ?: ''),
                    booleanParam(name: 'FETCH_ADDITIONAL_METADATA', value: checkFetchAdditionalMetadata(entity)),
                  ]

                  batches[batch_id][jobName] = {
                    echo "Triggering datadog-software-catalog-refresh-executor for ${entity.metadata.name}"
                    echo paramsList.toString()
                    def buildObj = build job: 'datadog-software-catalog-refresh-executor',
                                            parameters: paramsList,
                                            propagate: false,
                                            wait: true

                    results[jobName] = buildObj
                  }
                }

                batches.each { batch ->
                  parallel batch
                }

                echo "Collected downstream results:"
                results.each { name, b ->
                  echo "${name} -> number=${b.number}, result=${b.result}, id=${b.id}"
                }

                def failed = results.findAll { k, v -> v?.result != 'SUCCESS' }

                if (failed) {
                  def summary = failed.collect { k, v -> "${k}: #${v.number ?: 'unknown'} -> ${v.result}" }.join(', ')
                  echo "Some downstream jobs failed or were unstable: ${summary}"
                  error("Downstream failures: ${summary}")
                } else {
                  echo "All downstream jobs succeeded."
                }
              }
            }
          }
        }
      }
    }
  }
}

def checkFetchAdditionalMetadata(Map entity) {
  def tags = entity?.metadata?.tags
  if (tags && tags.any { it?.toString()?.contains('aws_account_id:') }) {
    return true
  }
  return entity?.kind == 'library'
}

def getEntitiesToRefresh(Boolean refreshAll, String entitiesToRefresh, String entityKind) {
  def entities = []
  if (refreshAll) {
    libraries = parseSoftwareCatalogCmd("list-libraries")
    services = parseSoftwareCatalogCmd("list-services")
    entities = libraries + services
  } else {
    def items = entitiesToRefresh.split(',')
    items.each { item ->
      entity = parseSoftwareCatalogCmd("get-${entityKind} --name ${item}")
      if (entity) {
        entities << entity
      } else {
        println "No software catalog ${entityKind} named ${item} has been found"
      }
    }
  }
  return entities
}

def parseSoftwareCatalogCmd(String cmd) {
  def baseCmd = """docker run \
    --rm \
    --pull always \
    -e TERM=dumb -e COLUMNS=2000 \
    -e Environment=shared \
    -e AWS_ACCESS_KEY_ID \
    -e AWS_SECRET_ACCESS_KEY \
    -e AWS_SESSION_TOKEN \
    086679231553.dkr.ecr.us-east-1.amazonaws.com/datadog-tools:latest \
    software-catalog """
  def jsonText = sh(script: baseCmd + cmd, returnStdout: true).trim()
  if (jsonText == 'null') {
    return [:]
  }
  def parsed = new JsonSlurper().parseText(jsonText)
  return toSerializable(parsed)
}

def toSerializable(obj) {
  if (obj == null) {
    return null
  }
  if (obj instanceof Map) {
    def res = new HashMap()
    obj.each { k, v -> res.put(k, toSerializable(v)) }
    return res
  }
  if (obj instanceof List) {
    def res = new ArrayList()
    obj.each { item -> res.add(toSerializable(item)) }
    return res
  }
  return obj
}
