const {
  InvalidParameterError, InvalidFilterError
} = require('./errors')

/**
 * A filter handle for model fields.
 * Used to construct filter expressions for iterating DB using query or scan.
 */
class Filter {
  /**
   * Create a filter for field
   * @param {String} iterationMethod The iteration method, one of query or scan
   * @param {String} fieldName The field's name.
   * @param {String} awsName The aws name for the field
   */
  constructor (iterationMethod, fieldName, awsName, keyType) {
    if (arguments.length !== 4) {
      throw new InvalidParameterError('Filter expects 4 constructor inputs')
    }
    if (!['query', 'scan'].includes(iterationMethod)) {
      throw new InvalidParameterError(
        'Filter must be created for query or scan')
    }
    if (!['PARTITION', 'SORT', undefined].includes(keyType)) {
      throw new InvalidParameterError(
        'keyType must be one of PARTITION, SORT and undefined')
    }
    this.__method = iterationMethod
    this.__fieldName = fieldName
    this.__awsName = awsName
    this.__keyType = keyType
    this.__locked = false
  }

  static VALID_OPERATIONS = new Set([
    '==', '!=', '<', '<=', '>', '>=', 'between', 'prefix', 'contains'
  ])

  /**
   * Impose a condition on the model field
   * @param {String} operation See VALID_OPERATIONS
   * @param {*} value The RHS of the condition
   */
  filter (operation, value) {
    if (this.__locked) {
      throw new InvalidFilterError(
        'Filter can no longer be changed')
    }
    if (this.__operation) {
      throw new InvalidFilterError(
        `Filter on field ${this.__fieldName} already exists`)
    }
    if (!this.constructor.VALID_OPERATIONS.has(operation)) {
      throw new InvalidFilterError(
        'Invalid filter operation. Valid operations are ' +
        this.constructor.VALID_OPERATIONS)
    }
    if (operation !== '==' && this.__keyType === 'PARTITION') {
      throw new InvalidFilterError(
        'Only equality filters are allowed on partition keys')
    }
    if (operation === '!=' && this.__keyType !== undefined) {
      throw new InvalidFilterError(
        'Inequality filters are not allowed on keys')
    }
    if (operation === 'prefix') {
      if (this.__keyType !== 'SORT') {
        throw new InvalidFilterError(
          'Prefix filters are only allowed on sort keys')
      }
      if (this.__method === 'scan') {
        throw new InvalidFilterError(
          'Invalid "prefix" operation for scan.')
      }
    }
    if (operation === 'between') {
      if (arguments.length !== 3) {
        throw new InvalidFilterError(
          `"between" operator requires 2 value inputs, e.g.
           query.${this.__fieldName}('between', lower, upper)`)
      }
      value = [arguments[1], arguments[2]]
      if (value[0] > value[1]) {
        throw new InvalidFilterError(
          'Input values for "between" operator must be in ascending order')
      }
    }
    if (operation === 'contains' && this.__keyType !== undefined) {
      throw new InvalidFilterError(
        '"contains" filters are not allowed on keys')
    }

    this.__operation = operation
    this.__value = value
    this.__createFilterExpression(operation, value)
  }

  /**
   * Lock the filter to prevent further modifications
   */
  lock () {
    this.__locked = true
  }

  __createFilterExpression (operation, value) {
    const awsName = this.__awsName
    if (operation === 'between') {
      this.conditions = [`#_${awsName} BETWEEN :_${awsName}Lower AND ` +
        `:_${awsName}Upper`]
      this.attrNames = { [`#_${awsName}`]: this.__fieldName }
      this.attrValues = {
        [`:_${awsName}Lower`]: value[0],
        [`:_${awsName}Upper`]: value[1]
      }
      return
    }
    if (operation === 'prefix') {
      this.conditions = [`begins_with(#_${awsName},:_${awsName})`]
      this.attrNames = { [`#_${awsName}`]: this.__fieldName }
      this.attrValues = { [`:_${awsName}`]: value }
      return
    }
    if (operation === 'contains') {
      this.conditions = [`contains(#_${awsName},:_${awsName})`]
      this.attrNames = { [`#_${awsName}`]: this.__fieldName }
      this.attrValues = { [`:_${awsName}`]: value }
      return
    }

    const operator = this.awsOperator
    this.conditions = [`#_${awsName}${operator}:_${awsName}`]
    this.attrNames = { [`#_${awsName}`]: this.__fieldName }
    this.attrValues = { [`:_${awsName}`]: value }
  }

  get awsOperator () {
    return {
      '==': '=',
      '!=': '<>',
      '<': '<',
      '<=': '<=',
      '>': '>',
      '>=': '>=',
      prefix: 'prefix',
      between: 'between',
      contains: 'contains'
    }[this.__operation]
  }
}

module.exports = Filter