import { test } from '@japa/runner'
import { DataImpactAnalysis } from '../../../../src/modeling/legacy/DataImpactAnalysis.js'
import {
  DataNamespaceKind,
  DataEntityKind,
  DataModelKind,
  DataPropertyKind,
  DataAssociationKind,
} from '../../../../src/models/kinds.js'
import { DataNamespace } from '../../../../src/modeling/legacy/DataNamespace.js'
import { DataEntity } from '../../../../src/modeling/legacy/DataEntity.js'
import { DataModel } from '../../../../src/modeling/legacy/DataModel.js'
import { DataProperty } from '../../../../src/modeling/legacy/DataProperty.js'
import { DataAssociation } from '../../../../src/modeling/legacy/DataAssociation.js'

test.group('DomainImpactAnalysis', (group) => {
  let root: DataNamespace
  let analyzer: DataImpactAnalysis
  let n1: DataNamespace
  let n2: DataNamespace
  let m1: DataModel
  let m2: DataModel
  let m3: DataModel
  let e1: DataEntity
  let e2: DataEntity
  let e3: DataEntity
  let e4: DataEntity
  let p1: DataProperty
  let a1: DataAssociation

  group.each.setup(() => {
    root = new DataNamespace()
    analyzer = new DataImpactAnalysis(root)
    n1 = root.addNamespace('n1')
    m1 = root.addDataModel('m1')
    n2 = n1.addNamespace('n2')
    m2 = n1.addDataModel('m2')
    e1 = m1.addEntity('e1')
    e2 = m2.addEntity('e2')
    m3 = n2.addDataModel('m3')
    e3 = m3.addEntity('e3')
    p1 = e1.addNamedProperty('p1')
    a1 = e1.addNamedAssociation('a1')
    a1.addTarget(e2)
    e2.addParent(e3.key)
    e3.addParent(e1.key)
    e4 = root.addDataModel('m4').addEntity('e4')
    e4.addParent(e3.key)
    // DataNamespace (root)
    // ├── DataNamespace (n1)
    // │   ├── DataModel (m2)
    // │   │   └── DataEntity (e2)
    // │   │       └── parent: DataEntity (e3)
    // │   └── DataNamespace (n2)
    // │       └── DataModel (m3)
    // │           └── DataEntity (e3)
    // │               └── parent: DataEntity (e1)
    // ├── DataModel (m1)
    // │   └── DataEntity (e1)
    // │       ├── DataProperty (p1)
    // │       └── DataAssociation (a1)
    // │           └── target: DataEntity (e2)
    // └── DataModel (m4)
    //     └── DataEntity (e4)
    //         └── parent: DataEntity (e3)
  })

  test('returns a report for deleting a namespace', ({ assert }) => {
    const report = analyzer.deleteAnalysis(n1.key, DataNamespaceKind)
    assert.equal(report.key, n1.key)
    assert.equal(report.kind, DataNamespaceKind)
    assert.isFalse(report.canProceed, 'operation should not be able to proceed')
    // Affected items:
    // - n1 (self)
    // - m2 (child)
    // - e2 (child), note, e3 parent is not included because it is included in the deletion.
    // - n2 (child)
    // - m3 (child)
    // - e3 (child)
    // - a1 (target of e2)
    // - e4 (parent of e3)
    assert.lengthOf(report.impact, 8, 'has all items')
    const [n1r, n2r, m3r, e3r, e4r, m2r, e2r, a1r] = report.impact

    assert.ok(n1r)
    assert.equal(n1r.key, n1.key)
    assert.equal(n1r.kind, DataNamespaceKind)
    assert.equal(n1r.type, 'delete')
    assert.equal(n1r.impact, `The n1 namespace will be deleted.`)
    assert.isFalse(n1r.blocking)

    assert.ok(n2r)
    assert.equal(n2r.key, n2.key)
    assert.equal(n2r.kind, DataNamespaceKind)
    assert.equal(n2r.type, 'delete')
    assert.equal(n2r.impact, `The n2 namespace will be deleted.`)
    assert.isFalse(n2r.blocking)

    assert.ok(m3r)
    assert.equal(m3r.key, m3.key)
    assert.equal(m3r.kind, DataModelKind)
    assert.equal(m3r.type, 'delete')
    assert.equal(m3r.impact, `The m3 data model will be deleted.`)
    assert.isFalse(m3r.blocking)

    assert.ok(e3r)
    assert.equal(e3r.key, e3.key)
    assert.equal(e3r.kind, DataEntityKind)
    assert.equal(e3r.type, 'delete')
    assert.equal(e3r.impact, `The e3 entity will be deleted.`)
    assert.isFalse(e3r.blocking)

    assert.ok(e4r)
    assert.equal(e4r.key, e4.key)
    assert.equal(e4r.kind, DataEntityKind)
    assert.equal(e4r.type, 'delete')
    assert.equal(e4r.impact, `The "e4" entity will become an orphan because it is a child of the "e3" entity.`)
    assert.equal(e4r.resolution, `The "e3" entity will be removed as the parent of the "e4" entity.`)
    assert.isTrue(e4r.blocking)
    assert.equal(e4r.relationship, 'child')

    assert.ok(m2r)
    assert.equal(m2r.key, m2.key)
    assert.equal(m2r.kind, DataModelKind)
    assert.equal(m2r.type, 'delete')
    assert.equal(m2r.impact, `The m2 data model will be deleted.`)
    assert.isFalse(m2r.blocking)

    assert.ok(e2r)
    assert.equal(e2r.key, e2.key)
    assert.equal(e2r.kind, DataEntityKind)
    assert.equal(e2r.type, 'delete')
    assert.equal(e2r.impact, `The e2 entity will be deleted.`)
    assert.isFalse(e2r.blocking)

    assert.ok(a1r)
    assert.equal(a1r.key, a1.key)
    assert.equal(a1r.kind, DataAssociationKind)
    assert.equal(a1r.type, 'delete')
    assert.equal(a1r.impact, `The a1 association will be broken because it has a target to e2.`)
    assert.equal(a1r.resolution, `The a1 association will be removed from e2.`)
    assert.isTrue(a1r.blocking)
  })

  test('returns a report for deleting a data model', ({ assert }) => {
    const report = analyzer.deleteAnalysis(m1.key, DataModelKind)
    assert.equal(report.key, m1.key)
    assert.equal(report.kind, DataModelKind)
    assert.isFalse(report.canProceed, 'operation should not be able to proceed')

    // Affected items:
    // - m1 (self)
    // - e1 (child)
    // - p1 (child of e1)
    // - a1 (child of e1)
    // - e3 (parent of e4)
    assert.lengthOf(report.impact, 5, 'has all items')
    const [m1r, e1r, e3r, p1r, a1r] = report.impact

    assert.ok(m1r)
    assert.equal(m1r.key, m1.key)
    assert.equal(m1r.kind, DataModelKind)
    assert.equal(m1r.type, 'delete')
    assert.equal(m1r.impact, `The m1 data model will be deleted.`)
    assert.isFalse(m1r.blocking)

    assert.ok(e1r)
    assert.equal(e1r.key, e1.key)
    assert.equal(e1r.kind, DataEntityKind)
    assert.equal(e1r.type, 'delete')
    assert.equal(e1r.impact, `The e1 entity will be deleted.`)
    assert.isFalse(e1r.blocking)

    assert.ok(p1r)
    assert.equal(p1r.key, p1.key)
    assert.equal(p1r.kind, DataPropertyKind)
    assert.equal(p1r.type, 'delete')
    assert.equal(p1r.impact, `The p1 property will be deleted.`)
    assert.isFalse(p1r.blocking)

    assert.ok(a1r)
    assert.equal(a1r.key, a1.key)
    assert.equal(a1r.kind, DataAssociationKind)
    assert.equal(a1r.type, 'delete')
    assert.equal(a1r.impact, `The a1 association will be deleted.`)
    assert.isFalse(a1r.blocking)

    assert.ok(e3r)
    assert.equal(e3r.key, e3.key)
    assert.equal(e3r.kind, DataEntityKind)
    assert.equal(e3r.type, 'delete')
    assert.equal(e3r.impact, `The "e3" entity will become an orphan because it is a child of the "e1" entity.`)
    assert.equal(e3r.resolution, `The "e1" entity will be removed as the parent of the "e3" entity.`)
    assert.isTrue(e3r.blocking)
    assert.equal(e3r.relationship, 'child')
  })

  test('returns a report for deleting an entity', ({ assert }) => {
    const report = analyzer.deleteAnalysis(e1.key, DataEntityKind)
    assert.equal(report.key, e1.key)
    assert.equal(report.kind, DataEntityKind)
    assert.isFalse(report.canProceed, 'operation should not be able to proceed')

    // Affected items:
    // - e1 (self)
    // - e3 (parent)
    // - p1 (child)
    // - a1 (child)
    assert.lengthOf(report.impact, 4, 'has all items')
    const [e1r, e3r, p1r, a1r] = report.impact

    assert.ok(e1r)
    assert.equal(e1r.key, e1.key)
    assert.equal(e1r.kind, DataEntityKind)
    assert.equal(e1r.type, 'delete')
    assert.equal(e1r.impact, `The e1 entity will be deleted.`)
    assert.isFalse(e1r.blocking)

    assert.ok(p1r)
    assert.equal(p1r.key, p1.key)
    assert.equal(p1r.kind, DataPropertyKind)
    assert.equal(p1r.type, 'delete')
    assert.equal(p1r.impact, `The p1 property will be deleted.`)
    assert.isFalse(p1r.blocking)

    assert.ok(a1r)
    assert.equal(a1r.key, a1.key)
    assert.equal(a1r.kind, DataAssociationKind)
    assert.equal(a1r.type, 'delete')
    assert.equal(a1r.impact, `The a1 association will be deleted.`)
    assert.isFalse(a1r.blocking)

    assert.ok(e3r)
    assert.equal(e3r.key, e3.key)
    assert.equal(e3r.kind, DataEntityKind)
    assert.equal(e3r.type, 'delete')
    assert.equal(e3r.impact, `The "e3" entity will become an orphan because it is a child of the "e1" entity.`)
    assert.equal(e3r.resolution, `The "e1" entity will be removed as the parent of the "e3" entity.`)
    assert.isTrue(e3r.blocking)
    assert.equal(e3r.relationship, 'child')
  })

  test('returns a report for deleting a property', ({ assert }) => {
    p1.getWebBinding().hidden = true
    const report = analyzer.deleteAnalysis(p1.key, DataPropertyKind)
    assert.equal(report.key, p1.key)
    assert.equal(report.kind, DataPropertyKind)
    assert.isTrue(report.canProceed, 'operation should be able to proceed')

    // Affected items:
    // - p1 (self)
    assert.lengthOf(report.impact, 1, 'has all items')
    const [p1r] = report.impact

    assert.ok(p1r)
    assert.equal(p1r.key, p1.key)
    assert.equal(p1r.kind, DataPropertyKind)
    assert.equal(p1r.type, 'delete')
    assert.equal(p1r.impact, `The p1 property will be deleted.`)
    assert.isFalse(p1r.blocking)
  })

  test('returns a report for deleting an association', ({ assert }) => {
    a1.getWebBinding().hidden = true
    const report = analyzer.deleteAnalysis(a1.key, DataAssociationKind)
    assert.equal(report.key, a1.key)
    assert.equal(report.kind, DataAssociationKind)
    assert.isTrue(report.canProceed, 'operation should be able to proceed')

    // Affected items:
    // - a1 (self)
    assert.lengthOf(report.impact, 1, 'has all items')
    const [a1r] = report.impact

    assert.ok(a1r)
    assert.equal(a1r.key, a1.key)
    assert.equal(a1r.kind, DataAssociationKind)
    assert.equal(a1r.type, 'delete')
    assert.equal(a1r.impact, `The a1 association will be deleted.`)
    assert.isFalse(a1r.blocking)
  })

  test('handles circular dependencies', ({ assert }) => {
    const report = analyzer.deleteAnalysis(e3.key, DataEntityKind)
    assert.equal(report.key, e3.key)
    assert.equal(report.kind, DataEntityKind)
    assert.isFalse(report.canProceed, 'operation should not be able to proceed')

    // Affected items:
    // - e3 (self)
    // - e2 (target of a1)
    // - e4 (child)
    assert.lengthOf(report.impact, 3, 'has all items')
    const [e3r, e2r, e4r] = report.impact

    assert.ok(e3r)
    assert.equal(e3r.key, e3.key)
    assert.equal(e3r.kind, DataEntityKind)
    assert.equal(e3r.type, 'delete')
    assert.equal(e3r.impact, `The e3 entity will be deleted.`)
    assert.isFalse(e3r.blocking)

    assert.ok(e2r)
    assert.equal(e2r.key, e2.key)
    assert.equal(e2r.kind, DataEntityKind)
    assert.equal(e2r.type, 'delete')
    assert.equal(e2r.impact, `The "e2" entity will become an orphan because it is a child of the "e3" entity.`)
    assert.equal(e2r.resolution, `The "e3" entity will be removed as the parent of the "e2" entity.`)
    assert.isTrue(e2r.blocking)
    assert.equal(e2r.relationship, 'child')

    assert.ok(e4r)
    assert.equal(e4r.key, e4.key)
    assert.equal(e4r.kind, DataEntityKind)
    assert.equal(e4r.type, 'delete')
    assert.equal(e4r.impact, `The "e4" entity will become an orphan because it is a child of the "e3" entity.`)
    assert.equal(e4r.resolution, `The "e3" entity will be removed as the parent of the "e4" entity.`)
    assert.isTrue(e4r.blocking)
    assert.equal(e4r.relationship, 'child')
  })

  test('deleteAnalysis() handles foreign namespaces', ({ assert }) => {
    const f1 = new DataNamespace()
    const fm1 = f1.addDataModel('fm1')
    const fe1 = fm1.addEntity('fe1')
    root.addForeign(f1)
    a1.addTarget(fe1)

    const report = analyzer.deleteAnalysis(e1.key, DataEntityKind)
    // console.log(JSON.stringify(report.impact, null, 2))
    assert.equal(report.key, e1.key)
    assert.equal(report.kind, DataEntityKind)
    assert.isFalse(report.canProceed, 'operation should not be able to proceed')

    // Affected items:
    // - e1 (self)
    // - e3 (parent)
    // - p1 (child)
    // - a1 (child)
    assert.lengthOf(report.impact, 4, 'has all items')
    const [e1r, e3r, p1r, a1r] = report.impact

    assert.ok(e1r)
    assert.equal(e1r.key, e1.key)
    assert.equal(e1r.kind, DataEntityKind)
    assert.equal(e1r.type, 'delete')
    assert.equal(e1r.impact, `The e1 entity will be deleted.`)
    assert.isFalse(e1r.blocking)

    assert.ok(p1r)
    assert.equal(p1r.key, p1.key)
    assert.equal(p1r.kind, DataPropertyKind)
    assert.equal(p1r.type, 'delete')
    assert.equal(p1r.impact, `The p1 property will be deleted.`)
    assert.isFalse(p1r.blocking)

    assert.ok(a1r)
    assert.equal(a1r.key, a1.key)
    assert.equal(a1r.kind, DataAssociationKind)
    assert.equal(a1r.type, 'delete')
    assert.equal(a1r.impact, `The a1 association will be deleted.`)
    assert.isFalse(a1r.blocking)

    assert.ok(e3r)
    assert.equal(e3r.key, e3.key)
    assert.equal(e3r.kind, DataEntityKind)
    assert.equal(e3r.type, 'delete')
    assert.equal(e3r.impact, `The "e3" entity will become an orphan because it is a child of the "e1" entity.`)
    assert.equal(e3r.resolution, `The "e1" entity will be removed as the parent of the "e3" entity.`)
    assert.isTrue(e3r.blocking)
    assert.equal(e3r.relationship, 'child')
  })
})
