import { validateAll } from "../src/templateApi/validate"
import createNodes from "../src/templateApi/createNodes"
import { some } from "lodash"
import { getNewField, addField } from "../src/templateApi/fields"
import {
  getNewRecordValidationRule,
  commonRecordValidationRules,
  addRecordValidationRule,
} from "../src/templateApi/recordValidationRules"
import { findField } from "../src/templateApi/hierarchy"
import { findCollectionDefaultIndex } from "./specHelpers"

const createValidHierarchy = () => {
  const root = createNodes.getNewRootLevel()

  const customerRecord = createNodes.getNewRecordTemplate(root, "customer")
  customerRecord.collectionName = "customers"

  const customersDefaultIndex = findCollectionDefaultIndex(customerRecord)
  const customersNoGroupaggregateGroup = createNodes.getNewAggregateGroupTemplate(
    customersDefaultIndex
  )
  customersNoGroupaggregateGroup.name = "Customers Summary"
  const allCustomersOwedFunctions = createNodes.getNewAggregateTemplate(
    customersNoGroupaggregateGroup
  )
  allCustomersOwedFunctions.aggregatedValue = "return record.owed"
  allCustomersOwedFunctions.name = "all customers owed amount"

  const partnerRecord = createNodes.getNewRecordTemplate(root, "partner")
  partnerRecord.collectionName = "partners"
  partnerRecord.name = "partner"
  const businessName = getNewField("string")
  businessName.name = "businessname"
  businessName.label = "bn"
  addField(partnerRecord, businessName)

  customerRecord.name = "customer"
  const surnameField = getNewField("string")
  surnameField.name = "surname"
  surnameField.label = "surname"
  const isaliveField = getNewField("bool")
  isaliveField.name = "isalive"
  const createddateField = getNewField("datetime")
  createddateField.name = "createddate"
  const ageField = getNewField("number")
  ageField.name = "age"
  const partnerField = getNewField("reference")
  partnerField.name = "partner"
  partnerField.typeOptions.indexNodeKey = "l"
  partnerField.typeOptions.reverseIndexNodeKeys = ["l"]
  partnerField.typeOptions.displayValue = "l"
  const otherNamesField = getNewField("array<string>")
  otherNamesField.name = "othernames"
  addField(customerRecord, surnameField)
  addField(customerRecord, isaliveField)
  addField(customerRecord, createddateField)
  addField(customerRecord, ageField)
  addField(customerRecord, partnerField)
  addField(customerRecord, otherNamesField)
  addRecordValidationRule(customerRecord)(
    commonRecordValidationRules.fieldNotEmpty("surname")
  )

  return {
    root,
    customerRecord,
    customersDefaultIndex,
    customersNoGroupaggregateGroup,
    allCustomersOwedFunctions,
  }
}

describe("hierarchy validation", () => {
  const expectInvalidField = (
    validationResult,
    fieldName,
    expectedNode,
    count = 1
  ) => {
    expect(validationResult.length).toBe(count)
    expect(
      some(
        validationResult,
        r => r.field === fieldName && r.item === expectedNode
      )
    ).toBe(true)
  }

  it("should return no errors when hierarchy is valid", () => {
    const hierarchy = createValidHierarchy()
    const validationResult = validateAll(hierarchy.root)
    expect(validationResult).toEqual([])
  })

  it("should return an error on name field, when name not set, on all nodes types", () => {
    let hierarchy = createValidHierarchy()
    const expectInvalidName = node =>
      expectInvalidField(validationResult, "name", node, 1)

    hierarchy = createValidHierarchy()
    hierarchy.customerRecord.name = ""
    let validationResult = validateAll(hierarchy.root)
    expectInvalidName(hierarchy.customerRecord)
    hierarchy.customerRecord.name = "customers"

    hierarchy = createValidHierarchy()
    hierarchy.customerRecord.name = ""
    validationResult = validateAll(hierarchy.root)
    expectInvalidName(hierarchy.customerRecord)
    hierarchy.customerRecord.name = "customer"
  })

  it("record > should return an error on fields member if empty", () => {
    const hierarchy = createValidHierarchy()
    hierarchy.customerRecord.fields = []
    const validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "fields", hierarchy.customerRecord)
  })

  it("record > should return an error on unrecognised type", () => {
    const hierarchy = createValidHierarchy()
    hierarchy.customerRecord.type = "notatype"
    const validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "type", hierarchy.customerRecord)
  })

  it("record > should return an error when validation rules do not have correct members", () => {
    let hierarchy = createValidHierarchy()
    delete hierarchy.customerRecord.validationRules[0].expressionWhenValid
    let validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "validationRules",
      hierarchy.customerRecord
    )

    hierarchy = createValidHierarchy()
    delete hierarchy.customerRecord.validationRules[0].messageWhenInvalid
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "validationRules",
      hierarchy.customerRecord
    )
  })

  it("collection > should return error when duplicate names", () => {
    const hierarchy = createValidHierarchy()
    hierarchy.customerRecord.collectionName = "partners"
    hierarchy.customerRecord.name = "partner"
    const validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "name", hierarchy.customerRecord, 2)
  })

  it("index > should return error when index has no map", () => {
    const hierarchy = createValidHierarchy()
    hierarchy.customersDefaultIndex.map = ""
    const validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "map", hierarchy.customersDefaultIndex)
  })

  it("index > should return error when index map function does not compile", () => {
    const hierarchy = createValidHierarchy()
    hierarchy.customersDefaultIndex.map = "invalid js!!"
    const validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "map", hierarchy.customersDefaultIndex)
  })

  it("index > should return error when index filter function does not compile", () => {
    const hierarchy = createValidHierarchy()
    hierarchy.customersDefaultIndex.filter = "invalid js!!"
    const validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "filter",
      hierarchy.customersDefaultIndex
    )
  })

  it("index > should return error when index type is not one of allowed values", () => {
    const hierarchy = createValidHierarchy()
    hierarchy.customersDefaultIndex.indexType = ""
    const validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "indexType",
      hierarchy.customersDefaultIndex
    )

    hierarchy.customersDefaultIndex.indexType = "should not be allowed"
    const validationResult2 = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult2,
      "indexType",
      hierarchy.customersDefaultIndex
    )
  })

  it("index > should return error when reference index's parent is not a record", () => {
    const hierarchy = createValidHierarchy()
    hierarchy.customersDefaultIndex.indexType = "reference"
    const validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "indexType",
      hierarchy.customersDefaultIndex
    )
  })

  it("field > should return error when a field is invalid", () => {
    const hierarchy = createValidHierarchy()
    const invalidField = hierarchy.customerRecord.fields[0]
    invalidField.name = ""
    const validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "name", invalidField)
  })

  it("aggregateGroup > should return error when name is not supplied", () => {
    const hierarchy = createValidHierarchy()
    hierarchy.customersNoGroupaggregateGroup.name = ""
    const validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "name",
      hierarchy.customersNoGroupaggregateGroup
    )
  })

  it("aggregate > should return error when name note set", () => {
    const hierarchy = createValidHierarchy()
    hierarchy.allCustomersOwedFunctions.name = ""
    const validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "name",
      hierarchy.allCustomersOwedFunctions
    )
  })

  it("aggregate > should return error when condition does not compile", () => {
    const hierarchy = createValidHierarchy()
    hierarchy.customersNoGroupaggregateGroup.condition = "invalid condition"
    const validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "condition",
      hierarchy.customersNoGroupaggregateGroup
    )
  })

  it("aggregate > should return error when aggregatedValue does not compile", () => {
    const hierarchy = createValidHierarchy()
    hierarchy.allCustomersOwedFunctions.aggregatedValue = "invalid value"
    const validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "aggregatedValue",
      hierarchy.allCustomersOwedFunctions
    )
  })

  it("aggregate > should be valid when valid condition and aggregatedValue supplied", () => {
    const hierarchy = createValidHierarchy()
    hierarchy.allCustomersOwedFunctions.aggregatedValue = "return record.owed;"
    hierarchy.allCustomersOwedFunctions.condition = "record.owed > 0;"
    const validationResult = validateAll(hierarchy.root)
    expect(validationResult.length).toBe(0)
  })

  it("field.typeOptions > string > should return error when maxLength <= 0", () => {
    const hierarchy = createValidHierarchy()
    const invalidField = findField(hierarchy.customerRecord, "surname")
    invalidField.typeOptions.maxLength = -1
    let validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "typeOptions.maxLength", invalidField)

    invalidField.typeOptions.maxLength = 0
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "typeOptions.maxLength", invalidField)

    invalidField.typeOptions.maxLength = 1
    validationResult = validateAll(hierarchy.root)
    validationResult.length === 0
  })

  it("field.typeOptions > string > should return error allowDeclaredValues only is not a bool", () => {
    const hierarchy = createValidHierarchy()
    const invalidField = findField(hierarchy.customerRecord, "surname")
    invalidField.typeOptions.allowDeclaredValuesOnly = null
    let validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "typeOptions.allowDeclaredValuesOnly",
      invalidField
    )

    invalidField.typeOptions.allowDeclaredValuesOnly = ""
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "typeOptions.allowDeclaredValuesOnly",
      invalidField
    )
  })

  it("field.typeOptions > string > should return error when values contains non-string", () => {
    const hierarchy = createValidHierarchy()
    const invalidField = findField(hierarchy.customerRecord, "surname")
    invalidField.typeOptions.values = [1]
    const validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "typeOptions.values", invalidField)
  })

  it("field.typeOptions > bool > should return error when allowNulls is not a bool", () => {
    const hierarchy = createValidHierarchy()
    const invalidField = findField(hierarchy.customerRecord, "isalive")
    invalidField.typeOptions.allowNulls = "1"
    let validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "typeOptions.allowNulls", invalidField)

    invalidField.typeOptions.allowNulls = null
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "typeOptions.allowNulls", invalidField)
  })

  it("field.typeOptions > datetime > should return error when maxValue is not a date", () => {
    const hierarchy = createValidHierarchy()
    const invalidField = findField(hierarchy.customerRecord, "createddate")
    invalidField.typeOptions.maxValue = "1"
    let validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "typeOptions.maxValue", invalidField)

    invalidField.typeOptions.maxValue = null
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "typeOptions.maxValue", invalidField)
  })

  it("field.typeOptions > datetime > should return error when minValue is not a date", () => {
    const hierarchy = createValidHierarchy()
    const invalidField = findField(hierarchy.customerRecord, "createddate")
    invalidField.typeOptions.minValue = "1"
    let validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "typeOptions.minValue", invalidField)

    invalidField.typeOptions.minValue = null
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "typeOptions.minValue", invalidField)
  })

  it("field.typeOptions > number > should return error when minValue is not an integer", () => {
    const hierarchy = createValidHierarchy()
    const invalidField = findField(hierarchy.customerRecord, "age")
    invalidField.typeOptions.minValue = "hello"
    let validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "typeOptions.minValue", invalidField)

    invalidField.typeOptions.minValue = null
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "typeOptions.minValue", invalidField)

    invalidField.typeOptions.minValue = 1.1
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "typeOptions.minValue", invalidField)
  })

  it("field.typeOptions > number > should return error when maxValue is not an integer", () => {
    const hierarchy = createValidHierarchy()
    const invalidField = findField(hierarchy.customerRecord, "age")
    invalidField.typeOptions.maxValue = "hello"
    let validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "typeOptions.maxValue", invalidField)

    invalidField.typeOptions.maxValue = null
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "typeOptions.maxValue", invalidField)

    invalidField.typeOptions.maxValue = 1.1
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(validationResult, "typeOptions.maxValue", invalidField)
  })

  it("field.typeOptions > number > should return error when decimal places is not a positive integer", () => {
    const hierarchy = createValidHierarchy()
    const invalidField = findField(hierarchy.customerRecord, "age")
    invalidField.typeOptions.decimalPlaces = "hello"
    let validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "typeOptions.decimalPlaces",
      invalidField
    )

    invalidField.typeOptions.decimalPlaces = null
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "typeOptions.decimalPlaces",
      invalidField
    )

    invalidField.typeOptions.decimalPlaces = -1
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "typeOptions.decimalPlaces",
      invalidField
    )

    invalidField.typeOptions.decimalPlaces = 1.1
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "typeOptions.decimalPlaces",
      invalidField
    )
  })

  it("field.typeOptions > reference > should return error when indexNodeKey is not a compmleted string", () => {
    const hierarchy = createValidHierarchy()
    const invalidField = findField(hierarchy.customerRecord, "partner")
    invalidField.typeOptions.indexNodeKey = null
    let validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "typeOptions.indexNodeKey",
      invalidField
    )

    invalidField.typeOptions.indexNodeKey = ""
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "typeOptions.indexNodeKey",
      invalidField
    )

    invalidField.typeOptions.indexNodeKey = 1
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "typeOptions.indexNodeKey",
      invalidField
    )
  })

  it("field.typeOptions > reference > should return error when reverseIndexNodeKeys is not a string array of >0 length", () => {
    const hierarchy = createValidHierarchy()
    const invalidField = findField(hierarchy.customerRecord, "partner")
    invalidField.typeOptions.reverseIndexNodeKeys = null
    let validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "typeOptions.reverseIndexNodeKeys",
      invalidField
    )

    invalidField.typeOptions.reverseIndexNodeKeys = ""
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "typeOptions.reverseIndexNodeKeys",
      invalidField
    )

    invalidField.typeOptions.reverseIndexNodeKeys = []
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "typeOptions.reverseIndexNodeKeys",
      invalidField
    )

    invalidField.typeOptions.reverseIndexNodeKeys = "/not/an/array"
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "typeOptions.reverseIndexNodeKeys",
      invalidField
    )

    invalidField.typeOptions.reverseIndexNodeKeys = ["/some/key/here"]
    validationResult = validateAll(hierarchy.root)
    expect(validationResult.length).toBe(0)
  })

  it("field.typeOptions > reference > should return error when displayValue is not a compmleted string", () => {
    const hierarchy = createValidHierarchy()
    const invalidField = findField(hierarchy.customerRecord, "partner")
    invalidField.typeOptions.displayValue = null
    let validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "typeOptions.displayValue",
      invalidField
    )

    invalidField.typeOptions.displayValue = ""
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "typeOptions.displayValue",
      invalidField
    )

    invalidField.typeOptions.displayValue = 1
    validationResult = validateAll(hierarchy.root)
    expectInvalidField(
      validationResult,
      "typeOptions.displayValue",
      invalidField
    )
  })
})