import { test } from '@japa/runner'
import { DataDomain, AssociationValidation } from '../../../../src/index.js'

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

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

  test('validate() should return an error when the association does not exist', ({ assert }) => {
    const results = validation.validate('nonExistentAssociation')
    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 entity1 = model.addEntity({ key: 'entity1', info: { name: 'Entity1' } })
    const entity2 = model.addEntity({ key: 'entity2', info: { name: 'Entity2' } })
    const association = entity1.addAssociation(
      { key: entity2.key },
      {
        key: 'association',
        info: { name: 'Association' },
      }
    )
    const validateNameSpy = sinon.spy(validation, 'validateName')
    const validateTargetsSpy = sinon.spy(validation, 'validateTargets')

    validation.validate(association)

    assert.calledWith(validateNameSpy, association)
    assert.calledWith(validateTargetsSpy, association)
    sinon.restore()
  })

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

  test('validateName() should return an error when the association name starts with a number', ({ assert }) => {
    const model = domain.addModel({ key: 'model' })
    const entity1 = model.addEntity({ key: 'entity1', info: { name: 'Entity1' } })
    const entity2 = model.addEntity({ key: 'entity2', info: { name: 'Entity2' } })
    const association = entity1.addAssociation(
      { key: entity2.key },
      {
        key: '1association',
        info: { name: '1association' },
      }
    )
    const results = validation.validateName(association)
    assert.lengthOf(results, 1)
    assert.equal(results[0].rule, 'format')
    assert.equal(results[0].severity, 'error')
  })

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

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

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

  test('validateName() should return no errors when the association name is valid', ({ assert }) => {
    const model = domain.addModel({ key: 'model' })
    const entity1 = model.addEntity({ key: 'entity1', info: { name: 'Entity1' } })
    const entity2 = model.addEntity({ key: 'entity2', info: { name: 'Entity2' } })
    const association = entity1.addAssociation(
      { key: entity2.key },
      {
        key: 'valid_association123',
        info: { name: 'valid_association123' },
      }
    )
    const results = validation.validateName(association)
    assert.lengthOf(results, 0)
  })

  test('validateEntities() should return an error when the target entity does not exist', ({ assert }) => {
    const model = domain.addModel({ key: 'model' })
    const entity1 = model.addEntity({ key: 'entity1', info: { name: 'Entity1' } })
    const association = entity1.addAssociation(
      {},
      {
        key: 'association',
        info: { name: 'association' },
      }
    )
    const results = validation.validateTargets(association)
    assert.lengthOf(results, 1)
    assert.equal(results[0].rule, 'required')
    assert.equal(results[0].severity, 'error')
  })
})
