let { dirname, join, sep } = require('path')
let { existsSync, mkdirSync, writeFileSync } = require('fs')
let { aliases, runtimes } = require('lambda-runtimes')
let { deepFrozenCopy } = require('@architect/utils')
let http = require('./templates/http')
let events = require('./templates/events')
let queues = require('./templates/queues')
let ws = require('./templates/ws')
let scheduled = require('./templates/scheduled')
let tablesStreams = require('./templates/tables-streams')

module.exports = async function writeCode (lambdae, inventory) {
  let { _project, plugins } = inventory.inv

  if (!Array.isArray(lambdae)) lambdae = [ lambdae ] // Normalize possible multi-tenant Lambdas
  for (let lambda of lambdae) {
    let { src, build, handlerFile, handlerModuleSystem, config, body } = lambda
    let { runtime, runtimeConfig } = config
    let pragma = lambda.pragma || 'customLambdas' // Lambdas generated by via set.customLambdas plugins have a null pragma

    let filepath = handlerFile
    if (handlerFile.includes(build)) {
      filepath = filepath.replace(build, src)
    }

    let handler = filepath.replace(process.cwd(), '')
    if (handler[0] === sep) handler = handler.substr(1)

    // Create handlers for registered + custom runtimes
    let registered = plugins?._methods?.create?.register?.find(r => {
      // We find the exact specified runtime, easy
      if (r.includes(runtime)) return true
      // Otherwise, we have to search the array for aliased values that match the runtime, ew
      return r.find(i => runtimes[aliases[i]]?.includes(runtime))
    })
    if (_project.customRuntimes?.runtimes?.includes(runtime) || registered) {
      let runtimePlugin = _project.customRuntimes.runtimePlugins[runtime] || registered._plugin
      let createMethod = plugins[runtimePlugin]?.create?.handlers
      if (!createMethod) {
        throw ReferenceError(`No create.handlers method found for custom runtime plugin '${runtime}' (${runtimePlugin})`)
      }
      // Make sure the handler directory exists should plugin authors be relying on it
      mkdirSync(lambda.src, { recursive: true })

      let frozen = deepFrozenCopy(inventory)
      let { arc } = frozen.inv._project
      let args = { arc, inventory: frozen, lambda: deepFrozenCopy(lambda) }
      let result = await createMethod(args)
      if (result) {
        let files = Array.isArray(result) ? result : [ result ]
        for (let file of files) {
          let { filename, body } = file
          if (!filename || !body) throw ReferenceError(`Cannot create handler file without 'filename' and 'body' properties (${runtimePlugin})`)
          let path = join(src, filename)
          let dir = dirname(path)
          // Don't assume they requested the file to be in the root of the handler, eh?
          if (!existsSync(dir)) {
            mkdirSync(dir, { recursive: true })
          }
          writeFileSync(path, body)
        }
      }
    }
    // Create handlers for built-in runtimes
    else {
      let configuredRuntime = runtimeConfig?.baseRuntime || aliases[runtime] || runtime
      let run
      if (configuredRuntime.startsWith('deno'))   run = 'deno'
      if (configuredRuntime.startsWith('node'))   run = 'node'
      if (configuredRuntime.startsWith('python')) run = 'python'
      if (configuredRuntime.startsWith('ruby'))   run = 'ruby'
      if (!run) throw ReferenceError(`Valid runtime not found: ${configuredRuntime}`)

      let types = { http, events, queues, ws, scheduled, 'tables-streams': tablesStreams, customLambdas: events }
      if (!body && pragma === 'http') {
        body = handlerModuleSystem
          ? types[pragma][run][handlerModuleSystem](handler)
          : types[pragma][run](handler)
      }
      else if (!body) {
        body = handlerModuleSystem
          ? types[pragma][run][handlerModuleSystem]
          : types[pragma][run]
      }
      writeFileSync(filepath, body)
    }
  }
}
