/* eslint-disable max-len */
import { test } from '@japa/runner'
import { DataDomain, EntityValidation } from '../../../../src/index.js'

test.group('EntityValidation', (group) => {
  let domain: DataDomain
  let validation: EntityValidation

  group.each.setup(() => {
    domain = new DataDomain()
    validation = new EntityValidation(domain)
  })

  test('validate() should return an error when the entity does not exist', ({ assert }) => {
    const results = validation.validate('nonExistentEntity')
    assert.lengthOf(results, 1)
    assert.equal(results[0].rule, 'exists')
    assert.equal(results[0].severity, 'error')
  })

  test('validate() should call other validation methods', ({ assert, sinon }) => {
    const model = domain.addModel({ key: 'model' })
    const entity = model.addEntity({ key: 'entity', info: { name: 'Entity' } })
    const primaryKeySpy = sinon.spy(validation, 'validatePrimaryKey')
    const minimumRequiredPropertiesSpy = sinon.spy(validation, 'minimumRequiredProperties')
    const validateNameSpy = sinon.spy(validation, 'validateName')
    const uniqueNameSpy = sinon.spy(validation, 'uniqueName')

    validation.validate(entity)

    assert.calledWith(primaryKeySpy, entity)
    assert.calledWith(minimumRequiredPropertiesSpy, entity)
    assert.calledWith(validateNameSpy, entity)
    assert.calledWith(uniqueNameSpy, entity)
    sinon.restore()
  })

  test('validatePrimaryKey() should return an error when the entity has no primary key', ({ assert }) => {
    const model = domain.addModel({ key: 'model' })
    const entity = model.addEntity({ key: 'entity', info: { name: 'Entity' } })
    const results = validation.validatePrimaryKey(entity)
    assert.lengthOf(results, 1)
    assert.equal(results[0].rule, 'primary_key')
    assert.equal(results[0].severity, 'error')
  })

  test('validatePrimaryKey() should return no errors when the entity has a primary key', ({ assert }) => {
    const model = domain.addModel({ key: 'model' })
    const entity = model.addEntity({ key: 'entity', info: { name: 'Entity' } })
    entity.addProperty({ key: 'id', type: 'string', primary: true })
    const results = validation.validatePrimaryKey(entity)
    assert.lengthOf(results, 0)
  })

  test('validatePrimaryKey() should return no errors when the entity inherits a primary key', ({ assert }) => {
    const model = domain.addModel({ key: 'model' })
    const parent = model.addEntity({ key: 'parent', info: { name: 'Parent' } })
    parent.addProperty({ key: 'id', type: 'string', primary: true })
    const entity = model.addEntity({ key: 'entity', info: { name: 'Entity' } })
    entity.addParent(parent.key)
    const results = validation.validatePrimaryKey(entity)
    assert.lengthOf(results, 0)
  })

  test('minimumRequiredProperties() should return a warning when the entity has no properties', ({ assert }) => {
    const model = domain.addModel({ key: 'model' })
    const entity = model.addEntity({ key: 'entity', info: { name: 'Entity' } })
    const results = validation.minimumRequiredProperties(entity)
    assert.lengthOf(results, 1)
    assert.equal(results[0].rule, 'required')
    assert.equal(results[0].severity, 'warning')
  })

  test('minimumRequiredProperties() should return no errors when the entity has properties', ({ assert }) => {
    const model = domain.addModel({ key: 'model' })
    const entity = model.addEntity({ key: 'entity', info: { name: 'Entity' } })
    entity.addProperty({ key: 'name', type: 'string' })
    const results = validation.minimumRequiredProperties(entity)
    assert.lengthOf(results, 0)
  })

  test('minimumRequiredProperties() should return no errors when the entity inherits properties from parent', ({
    assert,
  }) => {
    const model = domain.addModel({ key: 'model' })
    const parent = model.addEntity({ key: 'parent', info: { name: 'Parent' } })
    parent.addProperty({ key: 'name', type: 'string' })
    const entity = model.addEntity({ key: 'entity', info: { name: 'Entity' } })
    entity.addParent(parent.key)
    const results = validation.minimumRequiredProperties(entity)
    assert.lengthOf(results, 0)
  })

  test('minimumRequiredProperties() should return no errors when the entity inherits associations from parent', ({
    assert,
  }) => {
    const model = domain.addModel({ key: 'model' })
    const target = model.addEntity({ key: 'target', info: { name: 'Target' } })
    const parent = model.addEntity({ key: 'parent', info: { name: 'Parent' } })
    parent.addAssociation({ key: target.key })
    const entity = model.addEntity({ key: 'entity', info: { name: 'Entity' } })
    entity.addParent(parent.key)
    const results = validation.minimumRequiredProperties(entity)
    assert.lengthOf(results, 0)
  })

  test('minimumRequiredProperties() should return no errors when the entity inherits properties from grandparent', ({
    assert,
  }) => {
    const model = domain.addModel({ key: 'model' })
    const grandparent = model.addEntity({
      key: 'grandparent',
      info: { name: 'Grandparent' },
    })
    grandparent.addProperty({ key: 'name', type: 'string' })
    const parent = model.addEntity({ key: 'parent', info: { name: 'Parent' } })
    parent.addParent(grandparent.key)
    const entity = model.addEntity({ key: 'entity', info: { name: 'Entity' } })
    entity.addParent(parent.key)
    const results = validation.minimumRequiredProperties(entity)
    assert.lengthOf(results, 0)
  })

  test('minimumRequiredProperties() should return no errors when the entity inherits associations from grandparent', ({
    assert,
  }) => {
    const model = domain.addModel({ key: 'model' })
    const target = model.addEntity({ key: 'target', info: { name: 'Target' } })
    const grandparent = model.addEntity({
      key: 'grandparent',
      info: { name: 'Grandparent' },
    })
    grandparent.addAssociation({ key: target.key })
    const parent = model.addEntity({ key: 'parent', info: { name: 'Parent' } })
    parent.addParent(grandparent.key)
    const entity = model.addEntity({ key: 'entity', info: { name: 'Entity' } })
    entity.addParent(parent.key)
    const results = validation.minimumRequiredProperties(entity)
    assert.lengthOf(results, 0)
  })

  test('minimumRequiredProperties() should return no errors when the entity inherits properties from great-grandparent', ({
    assert,
  }) => {
    const model = domain.addModel({ key: 'model' })
    const greatGrandparent = model.addEntity({
      key: 'great_grandparent',
      info: { name: 'GreatGrandparent' },
    })
    greatGrandparent.addProperty({ key: 'name', type: 'string' })
    const grandparent = model.addEntity({ key: 'grandparent', info: { name: 'Grandparent' } })
    grandparent.addParent(greatGrandparent.key)
    const parent = model.addEntity({ key: 'parent', info: { name: 'Parent' } })
    parent.addParent(grandparent.key)
    const entity = model.addEntity({ key: 'entity', info: { name: 'Entity' } })
    entity.addParent(parent.key)
    const results = validation.minimumRequiredProperties(entity)
    assert.lengthOf(results, 0)
  })

  test('minimumRequiredProperties() should return warning when the entity has no properties in entire inheritance chain', ({
    assert,
  }) => {
    const model = domain.addModel({ key: 'model' })
    const grandparent = model.addEntity({ key: 'grandparent', info: { name: 'Grandparent' } })
    const parent = model.addEntity({ key: 'parent', info: { name: 'Parent' } })
    parent.addParent(grandparent.key)
    const entity = model.addEntity({ key: 'entity', info: { name: 'Entity' } })
    entity.addParent(parent.key)
    const results = validation.minimumRequiredProperties(entity)
    assert.lengthOf(results, 1)
    assert.equal(results[0].severity, 'warning')
    assert.equal(results[0].rule, 'required')
  })

  test('validateName() should return an error when the entity has no name', ({ assert }) => {
    const model = domain.addModel({ key: 'model' })
    const entity = model.addEntity({ key: 'entity', info: {} })
    entity.info.name = undefined
    const results = validation.validateName(entity)
    assert.lengthOf(results, 1)
    assert.equal(results[0].rule, 'required')
    assert.equal(results[0].severity, 'error')
  })

  test('validateName() should return an error when the entity name is too short', ({ assert }) => {
    const model = domain.addModel({ key: 'model' })
    const entity = model.addEntity({ key: 'entity', info: { name: 'a' } })
    const results = validation.validateName(entity)
    assert.lengthOf(results, 1)
    assert.equal(results[0].rule, 'length')
    assert.equal(results[0].severity, 'error')
  })

  test('validateName() should return an error when the entity name is too long', ({ assert }) => {
    const model = domain.addModel({ key: 'model' })
    const entity = model.addEntity({ key: 'entity', info: { name: 'a'.repeat(32) } })
    const results = validation.validateName(entity)
    assert.lengthOf(results, 1)
    assert.equal(results[0].rule, 'length')
    assert.equal(results[0].severity, 'error')
  })

  test('validateName() should return an error when the entity name has invalid characters', ({ assert }) => {
    const model = domain.addModel({ key: 'model' })
    const entity = model.addEntity({ key: 'entity', info: { name: 'invalid-name' } })
    const results = validation.validateName(entity)
    assert.lengthOf(results, 1)
    assert.equal(results[0].rule, 'format')
    assert.equal(results[0].severity, 'error')
  })

  test('validateName() should return an info when the entity name is not in snake case', ({ assert }) => {
    const model = domain.addModel({ key: 'model' })
    const entity = model.addEntity({ key: 'entity', info: { name: 'EntityName' } })
    const results = validation.validateName(entity)
    assert.lengthOf(results, 1)
    assert.equal(results[0].rule, 'snake_case')
    assert.equal(results[0].severity, 'info')
  })

  test('validateName() should return an error when the entity name is a reserved keyword', ({ assert }) => {
    const model = domain.addModel({ key: 'model' })
    const entity = model.addEntity({ key: 'entity', info: { name: 'select' } })
    const results = validation.validateName(entity)
    assert.lengthOf(results, 1)
    assert.equal(results[0].rule, 'reserved')
    assert.equal(results[0].severity, 'error')
  })

  test('validateName() should return no errors when the entity name is valid', ({ assert }) => {
    const model = domain.addModel({ key: 'model' })
    const entity = model.addEntity({ key: 'entity', info: { name: 'valid_name' } })
    const results = validation.validateName(entity)
    assert.lengthOf(results, 0)
  })

  test('uniqueName() should return an error when the entity name is not unique', ({ assert }) => {
    const model = domain.addModel({ key: 'model' })
    model.addEntity({ key: 'entity1', info: { name: 'entity_name' } })
    const entity2 = model.addEntity({ key: 'entity2', info: { name: 'entity_name' } })
    const results = validation.uniqueName(entity2)
    assert.lengthOf(results, 1)
    assert.equal(results[0].rule, 'unique')
    assert.equal(results[0].severity, 'error')
  })

  test('uniqueName() should return no errors when the entity name is unique', ({ assert }) => {
    const model = domain.addModel({ key: 'model' })
    model.addEntity({ key: 'entity1', info: { name: 'entity_name_1' } })
    const entity2 = model.addEntity({ key: 'entity2', info: { name: 'entity_name_2' } })
    const results = validation.uniqueName(entity2)
    assert.lengthOf(results, 0)
  })

  test('uniqueName() should return an error when the entity name is not unique (case insensitive)', ({ assert }) => {
    const model = domain.addModel({ key: 'model' })
    model.addEntity({ key: 'entity1', info: { name: 'Entity_Name1' } })
    const entity2 = model.addEntity({ key: 'entity2', info: { name: 'entity_name1' } })
    const results = validation.uniqueName(entity2)
    assert.lengthOf(results, 1)
    assert.equal(results[0].rule, 'unique')
    assert.equal(results[0].severity, 'error')
  })

  test('uniqueName() should return no errors when the entity name is not set', ({ assert }) => {
    const model = domain.addModel({ key: 'model' })
    model.addEntity({ key: 'entity1', info: { name: 'EntityName1' } })
    const entity2 = model.addEntity({ key: 'entity2', info: {} })
    const results = validation.uniqueName(entity2)
    assert.lengthOf(results, 0)
  })

  test('uniqueName() should return an error when the entity name is not unique in a foreign domain', ({ assert }) => {
    const foreignDomain = new DataDomain()
    const foreignModel = foreignDomain.addModel({ key: 'foreignModel' })
    foreignModel.addEntity({ key: 'foreignEntity', info: { name: 'entity_name' } })
    foreignDomain.info.version = '1.0.0'
    domain.registerForeignDomain(foreignDomain)
    const model = domain.addModel({ key: 'model' })
    const entity2 = model.addEntity({ key: 'entity2', info: { name: 'entity_name' } })
    const results = validation.uniqueName(entity2)
    assert.lengthOf(results, 1)
    assert.equal(results[0].rule, 'unique')
    assert.equal(results[0].severity, 'error')
  })
})
