import { test } from '@japa/runner'
import {
  DomainAssociationKind,
  DataDomain,
  DomainEntityKind,
  DomainModelKind,
  DomainNamespaceKind,
  DomainPropertyKind,
} from '../../../src/index.js'

test.group('DataDomain Serialization and Deserialization', () => {
  test('should serialize and deserialize an empty DataDomain', ({ assert }) => {
    const domain = new DataDomain()
    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized)

    assert.equal(restored.key, domain.key)
    assert.equal(restored.kind, domain.kind)
    assert.deepEqual(restored.info.toJSON(), domain.info.toJSON())
    assert.equal([...restored.graph.nodes()].length, 0)
    assert.equal([...restored.graph.edges()].length, 0)
  })

  test('should serialize and deserialize a DataDomain with namespaces', ({ assert }) => {
    const domain = new DataDomain()
    const ns1 = domain.addNamespace()
    const ns2 = ns1.addNamespace()

    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized)

    assert.equal(restored.key, domain.key)
    assert.equal(restored.kind, domain.kind)
    assert.deepEqual(restored.info.toJSON(), domain.info.toJSON())
    assert.equal([...restored.graph.nodes()].length, 2)
    assert.equal([...restored.graph.edges()].length, 0)

    const rn1 = restored.findNamespace(ns1.key)
    assert.isDefined(rn1)
    assert.equal(rn1!.key, ns1.key)
    assert.equal(rn1!.kind, DomainNamespaceKind)

    const rn2 = restored.findNamespace(ns2.key)
    assert.isDefined(rn2)
    assert.equal(rn2!.key, ns2.key)
    assert.equal(rn2!.kind, DomainNamespaceKind)
    assert.equal(restored.graph.parent(ns2.key), ns1.key)
  })

  test('should serialize and deserialize a DataDomain with models', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const m2 = domain.addModel()

    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized)

    assert.equal(restored.key, domain.key)
    assert.equal(restored.kind, domain.kind)
    assert.deepEqual(restored.info.toJSON(), domain.info.toJSON())
    assert.equal([...restored.graph.nodes()].length, 2)
    assert.equal([...restored.graph.edges()].length, 0)

    const rm1 = restored.findModel(m1.key)
    assert.isDefined(rm1)
    assert.equal(rm1!.key, m1.key)
    assert.equal(rm1!.kind, DomainModelKind)

    const rm2 = restored.findModel(m2.key)
    assert.isDefined(rm2)
    assert.equal(rm2!.key, m2.key)
    assert.equal(rm2!.kind, DomainModelKind)
  })

  test('should serialize and deserialize a DataDomain with entities', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    const e2 = m1.addEntity()
    e2.addParent(e1.key)

    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized)

    assert.equal(restored.key, domain.key)
    assert.equal(restored.kind, domain.kind)
    assert.deepEqual(restored.info.toJSON(), domain.info.toJSON())
    assert.equal([...restored.graph.nodes()].length, 3)
    assert.equal([...restored.graph.edges()].length, 1)

    const re1 = restored.findEntity(e1.key)
    assert.isDefined(re1)
    assert.equal(re1!.key, e1.key)
    assert.equal(re1!.kind, DomainEntityKind)

    const re2 = restored.findEntity(e2.key)
    assert.isDefined(re2)
    assert.equal(re2!.key, e2.key)
    assert.equal(re2!.kind, DomainEntityKind)
    assert.equal([...re2!.listParents()][0].key, e1.key)
  })

  test('should serialize and deserialize a DataDomain with properties', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    const p1 = e1.addProperty({ type: 'string' })
    const p2 = e1.addProperty({ type: 'number' })

    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized)

    assert.equal(restored.key, domain.key)
    assert.equal(restored.kind, domain.kind)
    assert.deepEqual(restored.info.toJSON(), domain.info.toJSON())
    assert.equal([...restored.graph.nodes()].length, 4)
    assert.equal([...restored.graph.edges()].length, 2)

    const rp1 = restored.findProperty(p1.key)
    assert.isDefined(rp1)
    assert.equal(rp1!.key, p1.key)
    assert.equal(rp1!.kind, DomainPropertyKind)

    const rp2 = restored.findProperty(p2.key)
    assert.isDefined(rp2)
    assert.equal(rp2!.key, p2.key)
    assert.equal(rp2!.kind, DomainPropertyKind)

    const re1 = restored.findEntity(e1.key)
    assert.isDefined(re1)
    const props = [...re1!.listProperties()]
    assert.equal(props.length, 2)
    assert.equal(props[0].key, p1.key)
    assert.equal(props[1].key, p2.key)
  })

  test('should serialize and deserialize a DataDomain with associations', ({ assert }) => {
    const domain = new DataDomain()
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    const e2 = m1.addEntity()
    const a1 = e1.addAssociation({ key: e2.key })

    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized)

    assert.equal(restored.key, domain.key)
    assert.equal(restored.kind, domain.kind)
    assert.deepEqual(restored.info.toJSON(), domain.info.toJSON())
    assert.equal([...restored.graph.nodes()].length, 4)
    assert.equal([...restored.graph.edges()].length, 2)

    const ra1 = restored.findAssociation(a1.key)
    assert.isDefined(ra1)
    assert.equal(ra1!.key, a1.key)
    assert.equal(ra1!.kind, DomainAssociationKind)
    assert.equal([...ra1!.listTargets()][0].key, e2.key)
  })

  test('should serialize and deserialize a complex DataDomain', ({ assert }) => {
    const domain = new DataDomain()
    const ns1 = domain.addNamespace()
    const ns2 = ns1.addNamespace()
    const m1 = ns1.addModel()
    const m2 = ns2.addModel()
    const e1 = m1.addEntity()
    const e2 = m2.addEntity()
    e2.addParent(e1.key)
    const p1 = e1.addProperty({ type: 'string' })
    const p2 = e2.addProperty({ type: 'number' })
    const a1 = e1.addAssociation({ key: e2.key })

    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized)

    assert.equal(restored.key, domain.key)
    assert.equal(restored.kind, domain.kind)
    assert.deepEqual(restored.info.toJSON(), domain.info.toJSON())
    assert.equal([...restored.graph.nodes()].length, 9)
    assert.equal([...restored.graph.edges()].length, 5)

    const rn1 = restored.findNamespace(ns1.key)
    assert.isDefined(rn1)
    assert.equal(rn1!.key, ns1.key)
    assert.equal(rn1!.kind, DomainNamespaceKind)
    assert.equal([...rn1!.listNamespaces()][0].key, ns2.key)
    assert.equal([...rn1!.listModels()][0].key, m1.key)

    const rn2 = restored.findNamespace(ns2.key)
    assert.isDefined(rn2)
    assert.equal(rn2!.key, ns2.key)
    assert.equal(rn2!.kind, DomainNamespaceKind)
    assert.equal(restored.graph.parent(ns2.key), ns1.key)
    assert.deepEqual([...rn2!.listNamespaces()], [])
    assert.equal([...rn2!.listModels()][0].key, m2.key)

    const rm1 = restored.findModel(m1.key)
    assert.isDefined(rm1)
    assert.equal(rm1!.key, m1.key)
    assert.equal(rm1!.kind, DomainModelKind)
    assert.equal(restored.graph.parent(m1.key), ns1.key)

    const rm2 = restored.findModel(m2.key)
    assert.isDefined(rm2)
    assert.equal(rm2!.key, m2.key)
    assert.equal(rm2!.kind, DomainModelKind)
    assert.equal(restored.graph.parent(m2.key), ns2.key)

    const re1 = restored.findEntity(e1.key)
    assert.isDefined(re1)
    assert.equal(re1!.key, e1.key)
    assert.equal(re1!.kind, DomainEntityKind)
    assert.equal(restored.graph.parent(e1.key), m1.key)
    assert.equal([...re1!.listProperties()][0].key, p1.key)

    const re2 = restored.findEntity(e2.key)
    assert.isDefined(re2)
    assert.equal(re2!.key, e2.key)
    assert.equal(re2!.kind, DomainEntityKind)
    assert.equal(restored.graph.parent(e2.key), m2.key)
    assert.equal([...re2!.listParents()][0].key, e1.key)
    assert.equal([...re2!.listProperties()][0].key, p2.key)

    const rp1 = restored.findProperty(p1.key)
    assert.isDefined(rp1)
    assert.equal(rp1!.key, p1.key)
    assert.equal(rp1!.kind, DomainPropertyKind)

    const rp2 = restored.findProperty(p2.key)
    assert.isDefined(rp2)
    assert.equal(rp2!.key, p2.key)
    assert.equal(rp2!.kind, DomainPropertyKind)

    const ra1 = restored.findAssociation(a1.key)
    assert.isDefined(ra1)
    assert.equal(ra1!.key, a1.key)
    assert.equal(ra1!.kind, DomainAssociationKind)
    assert.equal([...ra1!.listTargets()][0].key, e2.key)
  })

  test('should serialize and deserialize a DataDomain with foreign dependencies', ({ assert }) => {
    const fd = new DataDomain()
    const fm1 = fd.addModel({ key: 'fm1' })
    const fe1 = fm1.addEntity({ key: 'fe1' })
    fe1.addProperty({ type: 'string', key: 'fp1' })
    fd.info.version = '1.0.0'

    const domain = new DataDomain()
    domain.registerForeignDomain(fd)
    const m1 = domain.addModel({ key: 'm1' })
    const e1 = m1.addEntity({ key: 'e1' })
    const a1 = e1.addAssociation({ key: fe1.key, domain: fd.key }, { key: 'a1' })
    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized, [fd])

    assert.equal(restored.key, domain.key)
    assert.equal(restored.kind, domain.kind)
    assert.deepEqual(restored.info.toJSON(), domain.info.toJSON())
    assert.equal([...restored.graph.nodes()].length, 6)
    assert.equal([...restored.graph.edges()].length, 3)
    const rfe1 = restored.findForeignEntity(fe1.key, fd.key)
    assert.isDefined(rfe1)
    assert.equal(rfe1!.key, fe1.key)
    assert.equal(rfe1!.kind, DomainEntityKind)
    // Associations look like: Entity -> (edge) -> Association -> (edge) -> ForeignEntity
    assert.isTrue(restored.graph.hasEdge(e1.key, a1.key), 'has edge from e1 to a1')
    assert.isTrue(restored.graph.hasEdge(a1.key, `${fd.key}:${fe1.key}`), 'has edge from a1 to fe1')
  })

  test('should serialize and deserialize a DataDomain with missing foreign dependencies', ({ assert }) => {
    const fd = new DataDomain()
    fd.info.version = '1.0.0'
    const fm1 = fd.addModel()
    const fe1 = fm1.addEntity()
    fd.addProperty(fe1.key, { type: 'string' })

    const domain = new DataDomain()
    domain.registerForeignDomain(fd)
    const m1 = domain.addModel()
    const e1 = m1.addEntity()
    const a1 = e1.addAssociation({ key: fe1.key, domain: fd.key })

    const serialized = domain.toJSON()
    const restored = new DataDomain(serialized)

    assert.equal(restored.key, domain.key)
    assert.equal(restored.kind, domain.kind)
    assert.deepEqual(restored.info.toJSON(), domain.info.toJSON())
    assert.equal([...restored.graph.nodes()].length, 3)
    assert.equal([...restored.graph.edges()].length, 1)
    assert.isTrue(restored.graph.hasEdge(e1.key, a1.key), 'has edge from e1 to a1')
    assert.isFalse(restored.graph.hasEdge(a1.key, `${fd.key}:${fe1.key}`), 'has no edge from a1 to fe1')
  })
})
