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

test.group('DomainEntity.addProperty()', () => {
  test('adds a property to the entity', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty({ key: 'test-property', type: 'string' })
    assert.instanceOf(property, DomainProperty)
    assert.equal(property.kind, DomainPropertyKind)
    assert.equal(property.key, 'test-property')
    assert.deepInclude(property.info, { name: 'new_property' })
    assert.isTrue(dataDomain.graph.hasNode(property.key))
  })

  test('adds a property with default values', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty()
    assert.instanceOf(property, DomainProperty)
    assert.equal(property.kind, DomainPropertyKind)
    assert.typeOf(property.key, 'string')
    assert.isNotEmpty(property.key)
    assert.deepInclude(property.info, { name: 'new_property' })
    assert.isTrue(dataDomain.graph.hasNode(property.key))
  })

  test('throws an error if a property with the same key already exists', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const p1 = entity.addProperty()
    assert.throws(() => {
      entity.addProperty({ key: p1.key, type: 'string' })
    }, `Trying to add a property ${p1.key}, but it already exists`)
  })

  test('notifies change', async ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    entity.addProperty({ key: 'test-property', type: 'string' })
    await assert.dispatches(dataDomain, 'change', { timeout: 20 })
  })

  test('adds a property to the fields array', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({ key: 'test-property', type: 'string' })
    assert.deepEqual(entity.fields, [{ key: property.key, type: 'property' }])
  })

  test('adds a property to the root graph', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel({ key: 'test-model' })
    const entity = model.addEntity({ key: 'test-entity' })
    const property = entity.addProperty({ key: 'test-property', type: 'string' })
    assert.isTrue(dataDomain.graph.hasNode(property.key))
  })

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

test.group('DomainEntity.removeProperty()', () => {
  test('removes a property from the entity', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty({ key: 'test-property', type: 'string' })
    entity.removeProperty(property.key)
    assert.isFalse(dataDomain.graph.hasNode(property.key))
    assert.deepEqual(entity.fields, [])
  })

  test('throws an error if the property does not exist', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    assert.throws(() => {
      entity.removeProperty('non-existent-property')
    }, "Trying to remove the non-existent-property property, but it doesn't exist")
  })

  test('throws an error when the property belongs to another entity', ({ assert }) => {
    const dataDomain = new DataDomain()
    const m1 = dataDomain.addModel()
    const e1 = m1.addEntity()
    const e2 = m1.addEntity()
    const p1 = e1.addProperty()
    assert.throws(() => {
      e2.removeProperty(p1.key)
    }, `Trying to remove the ${p1.key} property, but it doesn't belong to this entity`)
  })

  test('notifies change', async ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty({ key: 'test-property', type: 'string' })
    entity.removeProperty(property.key)
    await assert.dispatches(dataDomain, 'change', { timeout: 20 })
  })

  test('removes the property from the fields array', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty({ key: 'test-property', type: 'string' })
    entity.removeProperty(property.key)
    assert.deepEqual(entity.fields, [])
  })

  test('removes the property from the root graph', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty({ key: 'test-property', type: 'string' })
    entity.removeProperty(property.key)
    assert.isFalse(dataDomain.graph.hasNode(property.key))
  })

  test('removes the graph edge', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property = entity.addProperty({ key: 'test-property', type: 'string' })
    entity.removeProperty(property.key)
    assert.isFalse(dataDomain.graph.hasEdge(entity.key, property.key))
  })

  test('removes only the specified property', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property1 = entity.addProperty({ key: 'test-property1', type: 'string' })
    const property2 = entity.addProperty({ key: 'test-property2', type: 'string' })
    entity.removeProperty(property1.key)
    assert.isFalse(dataDomain.graph.hasNode(property1.key))
    assert.isTrue(dataDomain.graph.hasNode(property2.key))
    assert.deepEqual(entity.fields, [{ key: property2.key, type: 'property' }])
  })

  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 property = entity1.addProperty({ key: 'test-property', type: 'string' })
    entity1.removeProperty(property.key)
    assert.isTrue(dataDomain.graph.hasNode(entity2.key))
    assert.isTrue(dataDomain.graph.hasNode(model.key))
  })

  test('does not remove other properties', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property1 = entity.addProperty({ key: 'test-property1', type: 'string' })
    const property2 = entity.addProperty({ key: 'test-property2', type: 'string' })
    entity.removeProperty(property1.key)
    assert.isFalse(dataDomain.graph.hasNode(property1.key))
    assert.isTrue(dataDomain.graph.hasNode(property2.key))
  })
})

test.group('DomainEntity.listProperties()', () => {
  test('lists all properties in the entity', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property1 = entity.addProperty({ key: 'test-property1', type: 'string' })
    const property2 = entity.addProperty({ key: 'test-property2', type: 'number' })
    const properties = [...entity.listProperties()]
    assert.lengthOf(properties, 2)
    assert.deepInclude(properties, property1)
    assert.deepInclude(properties, property2)
  })

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

  test('returns only properties', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property1 = entity.addProperty({ key: 'test-property1', type: 'string' })
    entity.addAssociation()
    const properties = [...entity.listProperties()]
    assert.lengthOf(properties, 1)
    assert.deepInclude(properties, property1)
  })

  test('properties in the list are of type DomainProperty', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    entity.addProperty({ key: 'test-property1', type: 'string' })
    const properties = [...entity.listProperties()]
    assert.instanceOf(properties[0], DomainProperty)
  })

  test('lists properties in the order they were added', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property1 = entity.addProperty({ key: 'test-property1', type: 'string' })
    const property2 = entity.addProperty({ key: 'test-property2', type: 'number' })
    const properties = [...entity.listProperties()]
    assert.deepEqual(properties, [property1, property2])
  })

  test('lists properties when there are no properties', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const properties = [...entity.listProperties()]
    assert.deepEqual(properties, [])
  })

  test('lists properties when there are other domain elements', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    const property1 = entity.addProperty({ key: 'test-property1', type: 'string' })
    entity.addAssociation()
    const properties = [...entity.listProperties()]
    assert.deepEqual(properties, [property1])
  })

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

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

test.group('DomainEntity.hasProperties()', () => {
  test('returns true if there are child properties', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    entity.addProperty({ key: 'test-property', type: 'string' })
    assert.isTrue(entity.hasProperties())
  })

  test('returns false if there are no child properties', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    assert.isFalse(entity.hasProperties())
  })

  test('returns false if there are only other domain elements', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    entity.addAssociation()
    assert.isFalse(entity.hasProperties())
  })

  test('returns true if there are both properties and other domain elements', ({ assert }) => {
    const dataDomain = new DataDomain()
    const model = dataDomain.addModel()
    const entity = model.addEntity()
    entity.addProperty({ key: 'test-property', type: 'string' })
    entity.addAssociation()
    assert.isTrue(entity.hasProperties())
  })

  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.hasProperties())
  })
})
