import { findHBSBlocks, processStringSync } from "@budibase/string-templates"
import { Integration } from "../../definitions/datasource"
import { DatasourcePlus } from "../base/datasourcePlus"

const CONST_CHAR_REGEX = new RegExp("'[^']*'", "g")

export function enrichQueryFields(
  fields: { [key: string]: any },
  parameters = {}
) {
  const enrichedQuery: { [key: string]: any } = Array.isArray(fields) ? [] : {}

  // enrich the fields with dynamic parameters
  for (let key of Object.keys(fields)) {
    if (fields[key] == null) {
      continue
    }
    if (typeof fields[key] === "object") {
      // enrich nested fields object
      enrichedQuery[key] = enrichQueryFields(fields[key], parameters)
    } else if (typeof fields[key] === "string") {
      // enrich string value as normal
      enrichedQuery[key] = processStringSync(fields[key], parameters, {
        noEscaping: true,
        noHelpers: true,
        escapeNewlines: true,
      })
    } else {
      enrichedQuery[key] = fields[key]
    }
  }
  if (
    enrichedQuery.json ||
    enrichedQuery.customData ||
    enrichedQuery.requestBody
  ) {
    try {
      enrichedQuery.json = JSON.parse(
        enrichedQuery.json ||
          enrichedQuery.customData ||
          enrichedQuery.requestBody
      )
    } catch (err) {
      // no json found, ignore
    }
    delete enrichedQuery.customData
  }
  return enrichedQuery
}

export function interpolateSQL(
  fields: { [key: string]: any },
  parameters: { [key: string]: any },
  integration: DatasourcePlus
) {
  let sql = fields.sql
  if (!sql || typeof sql !== "string") {
    return fields
  }
  const bindings = findHBSBlocks(sql)
  let variables = [],
    arrays = []
  for (let binding of bindings) {
    // look for array/list operations in the SQL statement, which will need handled later
    const listRegexMatch = sql.match(
      new RegExp(`(in|IN|In|iN)( )+[(]?${binding}[)]?`)
    )
    // check if the variable was used as part of a string concat e.g. 'Hello {{binding}}'
    // start by finding all the instances of const character strings
    const charConstMatch = sql.match(CONST_CHAR_REGEX) || []
    // now look within them to see if a binding is used
    const charConstBindingMatch = charConstMatch.find((string: any) =>
      string.match(new RegExp(`'[^']*${binding}[^']*'`))
    )
    if (charConstBindingMatch) {
      let [part1, part2] = charConstBindingMatch.split(binding)
      part1 = `'${part1.substring(1)}'`
      part2 = `'${part2.substring(0, part2.length - 1)}'`
      sql = sql.replace(
        charConstBindingMatch,
        integration.getStringConcat([
          part1,
          integration.getBindingIdentifier(),
          part2,
        ])
      )
    }
    // generate SQL parameterised array
    else if (listRegexMatch) {
      arrays.push(binding)
      // determine the length of the array
      const value = enrichQueryFields([binding], parameters)[0]
        .split(",")
        .map((val: string) => val.trim())
      // build a string like ($1, $2, $3)
      let replacement = `${Array.apply(null, Array(value.length))
        .map(() => integration.getBindingIdentifier())
        .join(",")}`
      // check if parentheses are needed
      if (!listRegexMatch[0].includes(`(${binding})`)) {
        replacement = `(${replacement})`
      }
      sql = sql.replace(binding, replacement)
    } else {
      sql = sql.replace(binding, integration.getBindingIdentifier())
    }
    variables.push(binding)
  }
  // replicate the knex structure
  fields.sql = sql
  fields.bindings = enrichQueryFields(variables, parameters)
  // check for arrays in the data
  let updated: string[] = []
  for (let i = 0; i < variables.length; i++) {
    if (arrays.includes(variables[i])) {
      updated = updated.concat(
        fields.bindings[i].split(",").map((val: string) => val.trim())
      )
    } else {
      updated.push(fields.bindings[i])
    }
  }
  fields.bindings = updated
  return fields
}
