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

test.group('DomainEntity.addAssociation()', () => {
  test('adds an association to the entity', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const association = entity.addAssociation()
    assert.instanceOf(association, DomainAssociation)
    assert.isTrue(dataDomain.graph.hasNode(association.key))
  })

  test('adds an association with a target entity', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity({ key: 'entity1' })
    const entity2 = model.addEntity({ key: 'entity2' })
    const association = entity1.addAssociation({ key: entity2.key })
    assert.instanceOf(association, DomainAssociation)
    assert.isTrue(dataDomain.graph.hasNode(association.key))
    assert.deepEqual(association.targets, [{ key: entity2.key }])
  })

  test('throws an error if the target entity does not exist', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    assert.throws(() => {
      entity.addAssociation({ key: 'non-existent-entity' })
    }, 'Target entity non-existent-entity not found')
  })

  test('adds an association with default values', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const association = entity.addAssociation()
    assert.instanceOf(association, DomainAssociation)
    assert.isTrue(dataDomain.graph.hasNode(association.key))
    assert.equal(association.info.name, 'new_association')
  })

  test('notifies change', async ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    entity.addAssociation()
    await assert.dispatches(dataDomain, 'change', { timeout: 20 })
  })

  test('adds an association to the fields array', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const association = entity.addAssociation()
    assert.deepEqual(entity.fields, [{ key: association.key, type: 'association' }])
  })

  test('adds an association to the root graph', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const association = entity.addAssociation()
    assert.isTrue(dataDomain.graph.hasNode(association.key))
  })

  test('creates a graph edge', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const association = entity.addAssociation()
    assert.isTrue(dataDomain.graph.hasEdge(entity.key, association.key))
    assert.deepEqual(dataDomain.graph.edge(entity.key, association.key), { type: 'association' })
  })

  test('adds association with a foreign target', ({ assert }) => {
    const root1 = new DataDomain()
    const root2 = new DataDomain()
    const m1 = root1.addModel()
    const m2 = root2.addModel()
    const e1 = m1.addEntity()
    const e2 = m2.addEntity()
    root2.info.version = '1.0.0'
    root1.registerForeignDomain(root2)

    const association = e1.addAssociation({ key: e2.key, domain: root2.key })
    assert.instanceOf(association, DomainAssociation)
    assert.isTrue(root1.graph.hasNode(association.key))
    assert.deepEqual(association.targets, [{ key: e2.key, domain: root2.key }])
  })

  test('throws an error if the foreign target entity does not exist', ({ assert }) => {
    const root1 = new DataDomain()
    const root2 = new DataDomain()
    const m1 = root1.addModel()
    const entity = m1.addEntity()
    root2.info.version = '1.0.0'
    root1.registerForeignDomain(root2)
    assert.throws(() => {
      entity.addAssociation({ key: 'non-existent-entity', domain: root2.key })
    }, `Foreign entity ${root2.key}:non-existent-entity not found`)
  })
})

test.group('DomainEntity.removeAssociation()', () => {
  test('removes an association from the entity', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const association = entity.addAssociation()
    entity.removeAssociation(association.key)
    assert.isFalse(dataDomain.graph.hasNode(association.key))
    assert.deepEqual(entity.fields, [])
  })

  test('throws an error if the association does not exist', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    assert.throws(() => {
      entity.removeAssociation('non-existent-association')
    }, 'Unable to find a connection between this entity and the non-existent-association association')
  })

  test('throws an error if the edge exists but is not an association', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity({ key: 'entity1' })
    const entity2 = model.addEntity({ key: 'entity2' })
    dataDomain.graph.setEdge(entity1.key, entity2.key, { type: 'parent' })

    assert.throws(() => {
      entity1.removeAssociation(entity2.key)
    }, 'Association entity2 does not exist')
  })

  test('notifies change', async ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const association = entity.addAssociation()
    entity.removeAssociation(association.key)
    await assert.dispatches(dataDomain, 'change', { timeout: 20 })
  })

  test('removes the association from the fields array', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const association = entity.addAssociation()
    entity.removeAssociation(association.key)
    assert.deepEqual(entity.fields, [])
  })

  test('removes the association from the root graph', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const association = entity.addAssociation()
    entity.removeAssociation(association.key)
    assert.isFalse(dataDomain.graph.hasNode(association.key))
  })

  test('removes the graph edge', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const association = entity.addAssociation()
    entity.removeAssociation(association.key)
    assert.isFalse(dataDomain.graph.hasEdge(entity.key, association.key))
  })

  test('removes only the specified association', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const association1 = entity.addAssociation()
    const association2 = entity.addAssociation()
    entity.removeAssociation(association1.key)
    assert.isFalse(dataDomain.graph.hasNode(association1.key))
    assert.isTrue(dataDomain.graph.hasNode(association2.key))
    assert.deepEqual(entity.fields, [{ key: association2.key, type: 'association' }])
  })

  test('does not remove other entities or models', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const entity2 = model.addEntity()
    const association = entity1.addAssociation()
    entity1.removeAssociation(association.key)
    assert.isTrue(dataDomain.graph.hasNode(entity2.key))
    assert.isTrue(dataDomain.graph.hasNode(model.key))
  })

  test('does not remove other associations', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const association1 = entity.addAssociation()
    const association2 = entity.addAssociation()
    entity.removeAssociation(association1.key)
    assert.isFalse(dataDomain.graph.hasNode(association1.key))
    assert.isTrue(dataDomain.graph.hasNode(association2.key))
  })
})

test.group('DomainEntity.listAssociations()', () => {
  test('lists all associations in the entity', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const association1 = entity.addAssociation()
    const association2 = entity.addAssociation()
    const associations = [...entity.listAssociations()]
    assert.lengthOf(associations, 2)
    assert.deepInclude(associations, association1)
    assert.deepInclude(associations, association2)
  })

  test('returns an empty array if there are no associations', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const associations = [...entity.listAssociations()]
    assert.lengthOf(associations, 0)
  })

  test('returns only associations', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const association1 = entity.addAssociation()
    entity.addProperty()
    const associations = [...entity.listAssociations()]
    assert.lengthOf(associations, 1)
    assert.deepInclude(associations, association1)
  })

  test('associations in the list are of type DomainAssociation', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    entity.addAssociation()
    const associations = [...entity.listAssociations()]
    assert.instanceOf(associations[0], DomainAssociation)
  })

  test('lists associations in the order they were added', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const association1 = entity.addAssociation()
    const association2 = entity.addAssociation()
    const associations = [...entity.listAssociations()]
    assert.deepEqual(associations, [association1, association2])
  })

  test('lists associations when there are other domain elements', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const association1 = entity.addAssociation()
    entity.addProperty()
    const associations = [...entity.listAssociations()]
    assert.deepEqual(associations, [association1])
  })

  test('lists associations when fields array is empty', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    entity.fields = []
    const associations = [...entity.listAssociations()]
    assert.deepEqual(associations, [])
  })

  test('lists associations when fields array contains only properties', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty()
    entity.fields = [{ key: property.key, type: 'property' }]
    const associations = [...entity.listAssociations()]
    assert.deepEqual(associations, [])
  })
})

test.group('DomainEntity.hasAssociations()', () => {
  test('returns true if the entity has associations', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    entity.addAssociation()
    assert.isTrue(entity.hasAssociations())
  })

  test('returns false if the entity has no associations', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    assert.isFalse(entity.hasAssociations())
  })

  test('returns false if the entity has other fields but not associations', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    entity.addProperty()
    assert.isFalse(entity.hasAssociations())
  })

  test('returns true if the entity has both properties and associations', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    entity.addProperty()
    entity.addAssociation()
    assert.isTrue(entity.hasAssociations())
  })

  test('returns false if the entity has no fields', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    entity.fields = []
    assert.isFalse(entity.hasAssociations())
  })
})
