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

test.group('DomainAssociation.addTarget()', () => {
  test('adds a target entity to the association', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const entity2 = model.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.addTarget(entity2)
    assert.deepEqual(association.targets, [{ key: entity2.key }])
    assert.isTrue(dataDomain.graph.hasEdge(association.key, entity2.key))
  }).tags(['@modeling', '@association', '@target', '@add'])

  test('adds a target entity by key to the association', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const entity2 = model.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.addTarget(entity2.key)
    assert.deepEqual(association.targets, [{ key: entity2.key }])
    assert.isTrue(dataDomain.graph.hasEdge(association.key, entity2.key))
  }).tags(['@modeling', '@association', '@target', '@add'])

  test('adds a target entity with a foreign namespace', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const foreignDomain = new DataDomain()
    foreignDomain.info.version = '1.0.0'
    dataDomain.registerForeignDomain(foreignDomain)
    const model2 = foreignDomain.addModel()
    const entity2 = model2.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.addTarget(entity2.key, foreignDomain.key)
    assert.deepEqual(association.targets, [{ key: entity2.key, domain: foreignDomain.key }])
    assert.isTrue(dataDomain.graph.hasEdge(association.key, `${foreignDomain.key}:${entity2.key}`))
  }).tags(['@modeling', '@association', '@target', '@add'])

  test('throws an error if the target entity already exists', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const entity2 = model.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.addTarget(entity2)
    assert.throws(() => {
      association.addTarget(entity2)
    }, `Target ${entity2.key} already exists.`)
  }).tags(['@modeling', '@association', '@target', '@add'])

  test('throws an error if the foreign namespace does not exist', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const entity2 = model.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    assert.throws(() => {
      association.addTarget(entity2.key, 'non-existent-namespace')
    }, 'Foreign namespace "non-existent-namespace" does not exist.')
  }).tags(['@modeling', '@association', '@target', '@add'])

  test('notifies change', async ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const entity2 = model.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.addTarget(entity2)
    await assert.dispatches(dataDomain, 'change', { timeout: 20 })
  }).tags(['@modeling', '@association', '@target', '@add'])

  test('creates a graph edge', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const entity2 = model.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.addTarget(entity2)
    assert.isTrue(dataDomain.graph.hasEdge(association.key, entity2.key))
    assert.deepEqual(dataDomain.graph.edge(association.key, entity2.key), { type: 'association' })
  }).tags(['@modeling', '@association', '@target', '@add'])

  test('adds a target entity to the association with a foreign domain', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const foreignDomain = new DataDomain()
    foreignDomain.info.version = '1.0.0'
    dataDomain.registerForeignDomain(foreignDomain)
    const model2 = foreignDomain.addModel()
    const entity2 = foreignDomain.addEntity(model2.key)
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.addTarget(entity2)
    assert.deepEqual(association.targets, [{ key: entity2.key, domain: foreignDomain.key }])
    assert.isTrue(dataDomain.graph.hasEdge(association.key, `${foreignDomain.key}:${entity2.key}`))
  }).tags(['@modeling', '@association', '@target', '@add'])

  test('adds a target entity to the association with a foreign domain - entity instance', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const foreignDomain = new DataDomain()
    foreignDomain.info.version = '1.0.0'
    dataDomain.registerForeignDomain(foreignDomain)
    const model2 = foreignDomain.addModel()
    const entity2 = foreignDomain.addEntity(model2.key)
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.addTarget(entity2)
    assert.deepEqual(association.targets, [{ key: entity2.key, domain: foreignDomain.key }])
    assert.isTrue(dataDomain.graph.hasEdge(association.key, `${foreignDomain.key}:${entity2.key}`))
  }).tags(['@modeling', '@association', '@target', '@add'])

  test('throws an error if the foreign namespace does not exist when adding a target entity with a foreign domain', ({
    assert,
  }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    assert.throws(() => {
      association.addTarget('test-entity', 'non-existent-domain')
    }, 'Foreign namespace "non-existent-domain" does not exist.')
  }).tags(['@modeling', '@association', '@target', '@add'])

  test('throws an error if the target entity already exists with the same foreign namespace', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const foreignDomain = new DataDomain()
    foreignDomain.info.version = '1.0.0'
    dataDomain.registerForeignDomain(foreignDomain)
    const model2 = foreignDomain.addModel()
    const entity2 = foreignDomain.addEntity(model2.key)
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.addTarget(entity2)
    assert.throws(() => {
      association.addTarget(entity2)
    }, `Target ${entity2.key} already exists.`)
  }).tags(['@modeling', '@association', '@target', '@add'])
})

test.group('DomainAssociation.removeTarget()', () => {
  test('removes a target entity from the targets list', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const entity2 = model.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.addTarget(entity2)
    association.removeTarget(entity2)
    assert.deepEqual(association.targets, [])
    assert.isFalse(dataDomain.graph.hasEdge(association.key, entity2.key))
  }).tags(['@modeling', '@association', '@target', '@remove'])

  test('removes a target entity by key from the targets list', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const entity2 = model.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.addTarget(entity2)
    association.removeTarget(entity2.key)
    assert.deepEqual(association.targets, [])
    assert.isFalse(dataDomain.graph.hasEdge(association.key, entity2.key))
  }).tags(['@modeling', '@association', '@target', '@remove'])

  test('does not throw an error if the target entity does not exist', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.removeTarget('non-existent-entity')
    assert.deepEqual(association.targets, [])
  }).tags(['@modeling', '@association', '@target', '@remove'])

  test('notifies change', async ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const entity2 = model.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.addTarget(entity2)
    association.removeTarget(entity2)
    await assert.dispatches(dataDomain, 'change', { timeout: 20 })
  }).tags(['@modeling', '@association', '@target', '@remove'])

  test('removes the graph edge', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const entity2 = model.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.addTarget(entity2)
    association.removeTarget(entity2)
    assert.isFalse(dataDomain.graph.hasEdge(association.key, entity2.key))
  }).tags(['@modeling', '@association', '@target', '@remove'])

  test('removes a target entity with a foreign namespace', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const foreignDomain = new DataDomain()
    foreignDomain.info.version = '1.0.0'
    dataDomain.registerForeignDomain(foreignDomain)
    const model2 = foreignDomain.addModel()
    const entity2 = model2.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.addTarget(entity2.key, foreignDomain.key)
    association.removeTarget(entity2.key)
    assert.deepEqual(association.targets, [])
    assert.isFalse(dataDomain.graph.hasEdge(association.key, entity2.key))
  }).tags(['@modeling', '@association', '@target', '@remove'])

  test('removes a target entity with a foreign namespace - entity instance', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const foreignDomain = new DataDomain()
    foreignDomain.info.version = '1.0.0'
    dataDomain.registerForeignDomain(foreignDomain)
    const model2 = foreignDomain.addModel()
    const entity2 = model2.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.addTarget(entity2.key, foreignDomain.key)
    association.removeTarget(entity2)
    assert.deepEqual(association.targets, [])
    assert.isFalse(dataDomain.graph.hasEdge(association.key, entity2.key))
  }).tags(['@modeling', '@association', '@target', '@remove'])

  test('removes only the specified target entity', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const entity2 = model.addEntity()
    const entity3 = model.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.addTarget(entity2)
    association.addTarget(entity3)
    association.removeTarget(entity2)
    assert.deepEqual(association.targets, [{ key: entity3.key }])
    assert.isFalse(dataDomain.graph.hasEdge(association.key, entity2.key))
    assert.isTrue(dataDomain.graph.hasEdge(association.key, entity3.key))
  }).tags(['@modeling', '@association', '@target', '@remove'])
})

test.group('DomainAssociation.listTargets()', () => {
  test('lists all target entities', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const entity2 = model.addEntity()
    const entity3 = model.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.addTarget(entity2)
    association.addTarget(entity3)
    const targets = [...association.listTargets()]
    assert.lengthOf(targets, 2)
    assert.deepInclude(targets, entity2)
    assert.deepInclude(targets, entity3)
  }).tags(['@modeling', '@association', '@target', '@list'])

  test('returns an empty array if there are no target entities', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    const targets = [...association.listTargets()]
    assert.lengthOf(targets, 0)
  }).tags(['@modeling', '@association', '@target', '@list'])

  test('returns only entities that exist in the graph', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const entity2 = model.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.addTarget(entity2)
    association.addTarget('non-existent-entity')
    const targets = [...association.listTargets()]
    assert.lengthOf(targets, 1)
    assert.deepInclude(targets, entity2)
  }).tags(['@modeling', '@association', '@target', '@list'])

  test('lists target entities with foreign namespaces', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const foreignDomain = new DataDomain()
    const model2 = foreignDomain.addModel()
    const entity2 = model2.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    foreignDomain.info.version = '1.0.0'
    dataDomain.registerForeignDomain(foreignDomain)
    association.addTarget(entity2.key, foreignDomain.key)

    const targets = [...association.listTargets()]
    assert.lengthOf(targets, 1)
    assert.deepInclude(targets, dataDomain.findForeignEntity(entity2.key, foreignDomain.key)!)
  }).tags(['@modeling', '@association', '@target', '@list'])

  test('lists target entities when some targets are from foreign namespaces and some are local', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const entity2 = model.addEntity()
    const foreignDomain = new DataDomain()
    const model2 = foreignDomain.addModel()
    const entity3 = model2.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    foreignDomain.info.version = '1.0.0'
    dataDomain.registerForeignDomain(foreignDomain)
    association.addTarget(entity2)
    association.addTarget(entity3.key, foreignDomain.key)
    const targets = [...association.listTargets()]
    assert.lengthOf(targets, 2)
    assert.deepInclude(targets, entity2)
    assert.deepInclude(targets, dataDomain.findForeignEntity(entity3.key, foreignDomain.key)!)
  }).tags(['@modeling', '@association', '@target', '@list'])

  test('lists target entities when the target entity is added as an instance', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const entity2 = model.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    association.addTarget(entity2)
    const targets = [...association.listTargets()]
    assert.lengthOf(targets, 1)
    assert.deepInclude(targets, entity2)
  }).tags(['@modeling', '@association', '@target', '@list'])

  test('lists target entities when the target entity is added as an instance from a foreign domain', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity1 = model.addEntity()
    const foreignDomain = new DataDomain()
    const model2 = foreignDomain.addModel()
    const entity2 = model2.addEntity()
    const association = new DomainAssociation(dataDomain, entity1.key)
    foreignDomain.info.version = '1.0.0'
    dataDomain.registerForeignDomain(foreignDomain)
    association.addTarget(entity2)
    const targets = [...association.listTargets()]
    assert.lengthOf(targets, 1)
    assert.deepInclude(targets, dataDomain.findForeignEntity(entity2.key, foreignDomain.key)!)
  }).tags(['@modeling', '@association', '@target', '@list'])
})
