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

test.group('DomainVersioning', () => {
  test('should serialize a domain with only local nodes', ({ assert }) => {
    const root = new DataDomain()
    const ns1 = root.addNamespace({ key: 'ns1' })
    const m1 = ns1.addModel({ key: 'm1' })
    m1.addEntity({ key: 'e1' })

    const serialized = DomainVersioning.serializeForVersionRelease(root)

    assert.lengthOf(serialized.nodes, 3)
    assert.lengthOf(serialized.edges, 0)
  })

  test('should include connected foreign nodes', ({ assert }) => {
    const root = new DataDomain()
    const ns1 = root.addNamespace({ key: 'ns1' })
    const m1 = ns1.addModel({ key: 'm1' })
    const e1 = m1.addEntity({ key: 'e1' })

    const fd = new DataDomain()
    const fm1 = fd.addModel({ key: 'fm1' })
    const fe1 = fm1.addEntity({ key: 'fe1' })
    fd.info.version = '1.0.0'
    root.registerForeignDomain(fd)

    e1.addAssociation({ domain: fd.key, key: fe1.key }, { key: 'a1' })

    const serialized = DomainVersioning.serializeForVersionRelease(root)

    assert.lengthOf(serialized.nodes, 5)
    assert.lengthOf(serialized.edges, 2)
    const foreignNode = serialized.nodes.find((n) => n.v === `${fd.key}:${fe1.key}`)
    assert.isDefined(foreignNode)
  })

  test('should exclude unconnected foreign nodes', ({ assert }) => {
    const root = new DataDomain()
    const ns1 = root.addNamespace({ key: 'ns1' })
    ns1.addModel({ key: 'm1' })

    const fd = new DataDomain()
    const fm1 = fd.addModel({ key: 'fm1' })
    const fe1 = fm1.addEntity({ key: 'fe1' })
    fd.info.version = '1.0.0'
    root.registerForeignDomain(fd)

    const serialized = DomainVersioning.serializeForVersionRelease(root)

    assert.lengthOf(serialized.nodes, 2)
    assert.lengthOf(serialized.edges, 0)
    const foreignNode = serialized.nodes.find((n) => n.v === `${fd.key}:${fe1.key}`)
    assert.isUndefined(foreignNode)
  })

  test('should handle indirect connections', ({ assert }) => {
    const root = new DataDomain()
    const n1 = root.addNamespace({ key: 'n1' })
    const m1 = n1.addModel({ key: 'm1' })
    const e1 = m1.addEntity({ key: 'e1' })

    const fd1 = new DataDomain()
    const fm1 = fd1.addModel({ key: 'fm1' })
    const fe1 = fm1.addEntity({ key: 'fe1' })
    const fe2 = fm1.addEntity({ key: 'fe2' }) // This should be included
    fe1.addAssociation({ key: fe2.key }, { key: 'a1' })
    fd1.info.version = '1.0.0'
    root.registerForeignDomain(fd1)

    e1.addAssociation({ domain: fd1.key, key: fe1.key }, { key: 'a2' })

    const serialized = DomainVersioning.serializeForVersionRelease(root)

    assert.lengthOf(serialized.nodes, 7)
    assert.lengthOf(serialized.edges, 4)
    const foreignNode1 = serialized.nodes.find((n) => n.v === `${fd1.key}:${fe1.key}`)
    assert.isDefined(foreignNode1)
    const foreignNode2 = serialized.nodes.find((n) => n.v === `${fd1.key}:${fe2.key}`)
    assert.isDefined(foreignNode2)
  })

  test('should serialize nested dependencies', ({ assert }) => {
    const root = new DataDomain()
    root.key = 'root'
    const n1 = root.addNamespace({ key: 'n1' }) // serialized
    const m1 = n1.addModel({ key: 'm1' }) // serialized
    const e1 = m1.addEntity({ key: 'e1' }) // serialized

    const fd1 = new DataDomain()
    fd1.key = 'fd1'
    const fm1 = fd1.addModel({ key: 'fm1' }) // not serialized
    const fe1 = fm1.addEntity({ key: 'fe1' }) // serialized
    fd1.info.version = '1.0.0'

    const fd2 = new DataDomain()
    fd2.key = 'fd2'
    fd2.registerForeignDomain(fd1)
    const fm2 = fd2.addModel({ key: 'fm2' }) // not serialized
    const fe2 = fm2.addEntity({ key: 'fe2' }) // serialized
    const fa1 = fe2.addAssociation({ domain: fd1.key, key: fe1.key }, { key: 'a1' })
    fd2.info.version = '1.0.0'
    root.registerForeignDomain(fd2) // serialized

    const a2 = e1.addAssociation({ domain: fd2.key, key: fe2.key }, { key: 'a2' }) // serialized

    const serialized = DomainVersioning.serializeForVersionRelease(root)

    assert.lengthOf(serialized.nodes, 7)
    assert.lengthOf(serialized.edges, 4)
    const sa1 = serialized.edges.find((e) => e.v === e1.key && e.w === a2.key)
    assert.isDefined(sa1)
    assert.deepEqual(sa1!.value, { type: 'association' })
    const sa2 = serialized.edges.find((e) => e.v === a2.key && e.w === `${fd2.key}:${fe2.key}`)
    assert.isDefined(sa2)
    assert.deepEqual(sa2!.value, { type: 'association', domain: fd2.key, foreign: true })
    const sa3 = serialized.edges.find((e) => e.v === `${fd2.key}:${fe2.key}` && e.w === `${fd2.key}:${fa1.key}`)
    assert.isDefined(sa3)
    assert.deepEqual(sa3!.value, { type: 'association', domain: fd2.key, foreign: true })
    const sa4 = serialized.edges.find(
      (e) => e.v === `${fd2.key}:${fa1.key}` && e.w === `${fd2.key}:${fd1.key}:${fe1.key}`
    )
    assert.isDefined(sa4)
    assert.deepEqual(sa4!.value, { type: 'association', domain: fd1.key, foreign: true })
    const sfe1 = serialized.nodes.find((n) => n.v === `${fd2.key}:${fd1.key}:${fe1.key}`)
    assert.isDefined(sfe1)
    const sfe2 = serialized.nodes.find((n) => n.v === `${fd2.key}:${fe2.key}`)
    assert.isDefined(sfe2)
    const sfa1 = serialized.nodes.find((n) => n.v === `${fd2.key}:${fa1.key}`)
    assert.isDefined(sfa1)
  })

  test('should serialize an empty graph', ({ assert }) => {
    const dataDomain = new DataDomain()
    const serialized = DomainVersioning.serializeForVersionRelease(dataDomain)
    assert.lengthOf(serialized.nodes, 0)
    assert.lengthOf(serialized.edges, 0)
  })
})
