/**
 * Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const builder = require('xmlbuilder')
const fs = require('fs').promises;
const path = require('path')
const assignMessage = require('../../policy_templates/assign-message/assign-message.js')
const verifyApiKey = require('../../policy_templates/security/apikey.js')
const oasValidation = require('../../policy_templates/validation/oas-validation.js')

module.exports = async function generateProxyEndPoint(apiProxy, options, api) {

  let destination = options.destination || path.join(__dirname, '../../../api_bundles')
  if (destination.substr(-1) === '/') {
    destination = destination.substr(0, destination.length - 1)
  }

  const oasSourceFileName = path.basename(options.source)
  const oasPath = options.source

  const rootDirectory = destination + '/' + apiProxy + '/apiproxy'
  const root = builder.create('ProxyEndpoint')
  root.att('name', 'default')
  root.ele('Description', {}, api.info.title)
  const preFlow = root.ele('PreFlow', { name: 'PreFlow' })

  // Add steps to preflow.
  requestPipe = preFlow.ele('Request')

  // If an API Security Scheme using API Key is defined in the OAS spec, generate and attach the relevant policies
  if (api.security && api.components.securitySchemes) {
    for (const apiSecurity of api.security) {
      if (Object.keys(apiSecurity) == 'ApiKeyAuth' && api.components.securitySchemes.ApiKeyAuth) {

        step = requestPipe.ele('Step', {})
        step.ele('Name', {}, 'va-verifyapikey')
        const flowCondition = 'request.verb != "OPTIONS"'
        step.ele('Condition').raw(flowCondition)

        // Create Policy
        const options = {};
        options.apiKeyName = api.components.securitySchemes.ApiKeyAuth.name
        options.keyRef = api.components.securitySchemes.ApiKeyAuth.in

        const xmlString = verifyApiKey.apiKeyGenTemplate(options)
        await fs.writeFile(rootDirectory + '/policies/' + 'va-verifyapikey' + '.xml', xmlString)
      }
    }
  }

  // If the OAS Validation option is true generate and attach the relevant policies
  if (options.oasValidation) {

    // Create OAS Validation policy
    let options = {};
    options.name = "oas-request-validation"
    options.resourceName = oasSourceFileName
    let xmlString = oasValidation.oasValidationTemplate(options)
    await fs.writeFile(rootDirectory + '/policies/' + options.name + '.xml', xmlString)
    await fs.copyFile(oasPath, rootDirectory + '/resources/oas/' + oasSourceFileName)

    // Add policy to PreFlow
    step = requestPipe.ele('Step', {})
    step.ele('Name', {}, options.name)
    const flowCondition = 'request.verb != "OPTIONS"'
    step.ele('Condition').raw(flowCondition)

    // Create Assign Message policy
    options = {};
    options.name = "am-bad-request-400"
    options.statusCode = "400"
    options.reasonPhrase = "Bad Request"

    xmlString = assignMessage.assignMessageTemplate(options)
    await fs.writeFile(rootDirectory + '/policies/' + options.name + '.xml', xmlString)

    // Add fault rule
    const faultRules = root.ele('FaultRules', {})
    const faultRule = faultRules.ele('FaultRule', { name: "OAS Request Validation Fault" })
    step = faultRule.ele('Step', {})
    step.ele('Name', {}, options.name)
    const faultCondition = 'OASValidation.oas-request-validation.failed = "true"'
    step.ele('Condition').raw(faultCondition)

  }

  preFlow.ele('Response')
  const flows = root.ele('Flows', {})

  for (const apiPath in api.paths) {
    if (Object.prototype.hasOwnProperty.call(api.paths, apiPath)) {

      for (const resource in api.paths[apiPath]) {
        if (Object.prototype.hasOwnProperty.call(api.paths[apiPath], resource)) {

          const resourceItem = api.paths[apiPath][resource]
          resourceItem.operationId = resourceItem.operationId || resource.toUpperCase() + ' ' + apiPath
          const flow = flows.ele('Flow', { name: resourceItem.operationId })
          const flowCondition = '(proxy.pathsuffix MatchesPath &quot;' + apiPath + '&quot;) and (request.verb = &quot;' + resource.toUpperCase() + '&quot;)'
          flow.ele('Condition').raw(flowCondition)
          flow.ele('Description', {}, resourceItem.summary)

          // Adding AM Policies to response
          responsePipe = flow.ele('Response')

          // Only create a mock response policy for the first example response provided for each path
          let responseCreated = false;

          Object.keys(resourceItem['responses']).forEach((key) => {
            if (!responseCreated) {

              step = responsePipe.ele('Step', {})
              const stepName = ('AM-' + resourceItem.operationId + '-' + key).replace(/\s+/g, '-').toLowerCase()
              step.ele('Name', {}, stepName)

              // Create Policy
              const options = {};
              options.name = stepName
              options.statusCode = key

              if (resourceItem['responses'][key].content != null) {
                options.payload = JSON.stringify(resourceItem['responses'][key].content["application/json"].example)
              } else {
                options.payload = '';
              }

              const xmlString = assignMessage.assignMessageTemplate(options)
              fs.writeFile(rootDirectory + '/policies/' + stepName + '.xml', xmlString)

              responseCreated = true;
            }
          });
        }
      } // for loop for resources ends here
    }
  }  // for loop for paths ends here

  const httpProxyConn = root.ele('HTTPProxyConnection')

  let specBasePath = undefined
  if (api.servers !== undefined && api.servers.length > 0 && api.servers[0].url !== undefined) {
    try {
      specBasePath = (new URL(api.servers[0].url, 'https://apigee.google.com')).pathname
    } catch (error) {
      // fail and use proxy name as fallback
    }
  }

  if (options.basepath) {
    httpProxyConn.ele('BasePath', {}, options.basepath)
  } else if (specBasePath !== undefined) {
    httpProxyConn.ele('BasePath', {}, specBasePath)
  } else {
    httpProxyConn.ele('BasePath', {}, '/' + apiProxy)
  }

  const virtualhosts = (options.virtualhosts) ? options.virtualhosts.split(',') : ['secure']
  virtualhosts.forEach(function (virtualhost) {
    httpProxyConn.ele('VirtualHost', {}, virtualhost)
  })

  // No back end, Assign message policies are used for responses, add noroute RouteRule
  root.ele('RouteRule', { name: 'noroute' })

  const xmlString = root.end({ pretty: true, indent: '  ', newline: '\n' })
  await fs.writeFile(rootDirectory + '/proxies/default.xml', xmlString)

  // Generate Base Configuration file
  const baseConfig = builder.create('APIProxy')
  baseConfig.att('name', apiProxy)
  const baseXml = baseConfig.end({ pretty: true, indent: '  ', newline: '\n' })
  await fs.writeFile(rootDirectory + '/' + apiProxy + '.xml', baseXml)
}