/* eslint-disable max-len */
import { test } from '@japa/runner'
import { DataDomain, DomainEntity, DomainModel, DomainProperty } from '../../../src/index.js'

// const rfc2616Re =
//   /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), ([0-3][0-9]) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ([0-9]{4}) ([01][0-9]|2[0-3])(:[0-5][0-9]){2} GMT$/
const rfc3339Re = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$/
const dateRe = /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/
const timeRe = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}$/
const mime = 'application/json'

test.group('JSON DomainEntity Generator: string', (group) => {
  let e1: DomainEntity

  group.each.setup(() => {
    const ns = new DataDomain()
    const d1 = ns.addModel({ key: 'd1', info: { name: 'd1' } })
    e1 = d1.addEntity({ key: 'e1', info: { name: 'e1' } })
  })

  test('generates an example', ({ assert }) => {
    e1.addProperty({ key: 'p1', info: { name: 'p1' } })
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result as string)
    assert.typeOf(data.p1, 'string', 'has the property value')
  })

  test('has min length with schema', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', info: { name: 'p1' } })
    p1.schema = {
      minimum: 10,
    }

    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result as string)
    assert.typeOf(data.p1, 'string', 'has the property value')
    assert.isAtLeast(data.p1.length, 10, 'has the minimum length')
  })

  test('has max length with schema', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', info: { name: 'p1' } })
    p1.schema = {
      maximum: 10,
    }

    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result as string)
    assert.typeOf(data.p1, 'string', 'has the property value')
    assert.isAtMost(data.p1.length, 10, 'has the maximum length')
  })

  test('returns an array when multiple property', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', info: { name: 'p1' } })
    p1.multiple = true
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result as string)
    assert.typeOf(data.p1, 'array', 'has the array value')
    assert.lengthOf(data.p1, 1, 'has a single value')
    assert.typeOf(data.p1[0], 'string', 'has a final value correct type')
  })

  test('returns example value when defined on the mime type scheme', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', info: { name: 'p1' } })
    p1.schema = {
      minimum: 1,
      maximum: 3,
      examples: ['test123'],
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result as string)
    assert.equal(data.p1, 'test123', 'has the example value')
  })

  test('returns all example values with multiple property', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', info: { name: 'p1' } })
    p1.multiple = true
    p1.schema = {
      examples: ['test123', '123test'],
    }

    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result as string)
    assert.deepEqual(data.p1, ['test123', '123test'], 'has the examples value')
  })

  test('returns the default value when defined', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', info: { name: 'p1' } })
    p1.schema = {
      defaultValue: { value: 'test123', type: 'literal' },
    }

    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result as string)
    assert.deepEqual(data.p1, 'test123', 'has the default value')
  })

  test('returns the default value when multiple property', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', info: { name: 'p1' } })
    p1.multiple = true
    p1.schema = {
      defaultValue: { value: 'test123', type: 'literal' },
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result as string)
    assert.deepEqual(data.p1, ['test123'], 'has the default value')
  })

  test('respects both the min and max length', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', info: { name: 'p1' } })
    p1.schema = {
      minimum: 15,
      maximum: 30,
    }

    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result as string)
    assert.typeOf(data.p1, 'string', 'has the property value')
    assert.isAtLeast(data.p1.length, 15, 'has the minimum length')
    assert.isAtMost(data.p1.length, 30, 'has the maximum length')
  })

  test('respects the hidden binding', ({ assert }) => {
    e1.addProperty({ key: 'p1', info: { name: 'p1' } })
    const p2 = e1.addProperty({ key: 'p2', info: { name: 'p2' } })
    p2.getWebBinding().hidden = true
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result as string)
    assert.typeOf(data.p1, 'string', 'has the property value')
    assert.isUndefined(data.p2, 'hidden property is not set')
  })
})

test.group('JSON DomainEntity Generator: number', (group) => {
  let e1: DomainEntity

  group.each.setup(() => {
    const ns = new DataDomain()
    const d1 = ns.addModel({ key: 'd1', info: { name: 'd1' } })
    e1 = d1.addEntity({ key: 'e1', info: { name: 'e1' } })
  })

  test('generates an example value', ({ assert }) => {
    e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.p1, 'number', 'has the property number')
  })

  test('has the minimum value', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.schema = {
      minimum: 10,
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.isAtLeast(data.p1, 10, 'has the minimum length')
  })

  test('has the maximum value', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.schema = {
      maximum: 10,
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.isAtMost(data.p1, 10, 'has the maximum length')
  })

  test('respects both the min and max', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.schema = {
      minimum: 5,
      maximum: 10,
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.isAtLeast(data.p1, 5, 'has the minimum length')
    assert.isAtMost(data.p1, 10, 'has the maximum length')
  })

  // Note, this may randomly fail because the data generation library does not
  // guarantee to always generate a float (it does generate a float but with `.00` value).
  test('generates a floating-point number for a float format', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.bindings = [
      {
        type: 'web',
        schema: {
          format: 'float',
        },
      },
    ]
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.isNotNaN(data)
    // const rest = data.p1 % 1;
    // assert.isAbove(rest, 0, 'module is above 0');
    // assert.isBelow(rest, 1, 'module is below 0');
  })

  test('generates a floating-point number for a double format', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.bindings = [
      {
        type: 'web',
        schema: {
          format: 'double',
        },
      },
    ]
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.isNotNaN(data.p1)
    // const rest = data.p1 % 1;
    // assert.isAbove(rest, 0, 'module is above 0');
    // assert.isBelow(rest, 1, 'module is below 0');
  })

  test('returns an array number property when multiple property', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.multiple = true
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.p1, 'array', 'has the array value')
    assert.lengthOf(data.p1, 1, 'has a single value')
    assert.typeOf(data.p1[0], 'number', 'has a final value correct type')
  })

  test('respects the "multipleOf" schema property', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.schema = {
      multipleOf: 5,
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.equal(data.p1 % 5, 0, 'has a value multipliable by 5')
  })

  test('returns the example value', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.schema = {
      examples: ['1', '2', '3'],
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.equal(data.p1, 1, 'has the first example')
  })

  test('returns all examples when multiple property', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.multiple = true
    p1.schema = {
      examples: ['1', '2', '3'],
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.deepEqual(data.p1, [1, 2, 3], 'has the first example')
  })

  test('returns the default value', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.schema = {
      defaultValue: { value: '123', type: 'literal' },
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.deepEqual(data.p1, 123, 'has the first example')
  })
})

test.group('JSON DomainEntity Generator: int64 format', (group) => {
  let e1: DomainEntity
  let p1: DomainProperty

  group.each.setup(() => {
    const ns = new DataDomain()
    const d1 = ns.addModel({ key: 'd1', info: { name: 'd1' } })
    e1 = d1.addEntity({ key: 'e1', info: { name: 'e1' } })
    p1 = e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    p1.getWebBinding().format = 'int64'
  })

  test('generates an example value', ({ assert }) => {
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.p1, 'number', 'has the property number')
  })

  test('has the minimum value', ({ assert }) => {
    p1.schema = {
      minimum: 10,
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.isAtLeast(data.p1, 10, 'has the minimum length')
  })

  test('has the maximum value', ({ assert }) => {
    p1.schema = {
      maximum: 10,
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.isAtMost(data.p1, 10, 'has the maximum length')
  })

  test('respects both the min and max', ({ assert }) => {
    p1.schema = {
      minimum: 5,
      maximum: 10,
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.isAtLeast(data.p1, 5, 'has the minimum length')
    assert.isAtMost(data.p1, 10, 'has the maximum length')
  })

  test('generates an integer for an int32 format', ({ assert }) => {
    p1.bindings = [
      {
        type: 'web',
        schema: {
          format: 'int32',
        },
      },
    ]
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    const rest = data.p1 % 1
    assert.equal(rest, 0, 'module is 0')
  })

  test('generates an integer for an int64 format', ({ assert }) => {
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    const rest = data.p1 % 1
    assert.equal(rest, 0, 'module is 0')
  })

  test('returns an array number property when multiple property', ({ assert }) => {
    p1.multiple = true
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.p1, 'array', 'has the array value')
    assert.lengthOf(data.p1, 1, 'has a single value')
    assert.typeOf(data.p1[0], 'number', 'has a final value correct type')
  })

  test('respects the "multipleOf" schema property', ({ assert }) => {
    p1.schema = {
      multipleOf: 5,
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.equal(data.p1 % 5, 0, 'has a value multipliable by 5')
  })

  test('returns the example value', ({ assert }) => {
    p1.schema = {
      examples: ['1', '2', '3'],
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.equal(data.p1, 1, 'has the first example')
  })

  test('returns all examples when multiple property', ({ assert }) => {
    p1.multiple = true
    p1.schema = {
      examples: ['1', '2', '3'],
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.deepEqual(data.p1, [1, 2, 3], 'has the first example')
  })

  test('returns the default value', ({ assert }) => {
    p1.schema = {
      defaultValue: { value: '123', type: 'literal' },
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.deepEqual(data.p1, 123, 'has the first example')
  })
})

test.group('JSON DomainEntity Generator: date', (group) => {
  let e1: DomainEntity

  group.each.setup(() => {
    const ns = new DataDomain()
    const d1 = ns.addModel({ key: 'd1', info: { name: 'd1' } })
    e1 = d1.addEntity({ key: 'e1', info: { name: 'e1' } })
  })

  test('generates an example', ({ assert }) => {
    e1.addProperty({ key: 'p1', type: 'date', info: { name: 'p1' } })
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.p1, 'string', 'has the string value')
    assert.match(data.p1, dateRe)
  })

  test('generates an example with the multiple property', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'date', info: { name: 'p1' } })
    p1.multiple = true
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.p1, 'array', 'has the array value')
    assert.lengthOf(data.p1, 1, 'has a single value')
    assert.match(data.p1[0], dateRe)
  })

  test('returns the default value', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'date', info: { name: 'p1' } })
    p1.schema = {
      defaultValue: { value: '2022-08-08', type: 'literal' },
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.equal(data.p1, '2022-08-08')
  })

  test('returns the default value as array with multiple property', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'date', info: { name: 'p1' } })
    p1.multiple = true
    p1.schema = {
      defaultValue: { value: '2022-08-08', type: 'literal' },
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.deepEqual(data.p1, ['2022-08-08'])
  })

  test('returns the example value', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'date', info: { name: 'p1' } })
    p1.schema = {
      examples: ['2022-08-08', '2022-08-09'],
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.equal(data.p1, '2022-08-08')
  })

  test('returns the default value as array with multiple property', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'date', info: { name: 'p1' } })
    p1.multiple = true
    p1.schema = {
      examples: ['2022-08-08', '2022-08-09'],
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.deepEqual(data.p1, ['2022-08-08', '2022-08-09'])
  })
})

test.group('JSON DomainEntity Generator: datetime', (group) => {
  let e1: DomainEntity

  group.each.setup(() => {
    const ns = new DataDomain()
    const d1 = ns.addModel({ key: 'd1', info: { name: 'd1' } })
    e1 = d1.addEntity({ key: 'e1', info: { name: 'e1' } })
  })

  test('generates an example with default format', ({ assert }) => {
    e1.addProperty({ key: 'p1', type: 'datetime', info: { name: 'p1' } })
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.p1, 'string', 'has the string value')
    assert.match(data.p1, rfc3339Re)
  })

  test('returns the default value', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'datetime', info: { name: 'p1' } })
    p1.schema = {
      defaultValue: { value: '2036-01-04T19:49:30.224Z', type: 'literal' },
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.equal(data.p1, '2036-01-04T19:49:30.224Z')
  })

  test('returns the default value as array with multiple property', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'datetime', info: { name: 'p1' } })
    p1.multiple = true
    p1.schema = {
      defaultValue: { value: '2036-01-04T19:49:30.224Z', type: 'literal' },
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.deepEqual(data.p1, ['2036-01-04T19:49:30.224Z'])
  })

  test('returns the example value', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'datetime', info: { name: 'p1' } })
    p1.schema = {
      examples: ['2036-01-04T19:49:30.224Z', '2036-01-05T19:49:30.224Z'],
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.equal(data.p1, '2036-01-04T19:49:30.224Z')
  })

  test('returns the default value as array with multiple property', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'datetime', info: { name: 'p1' } })
    p1.multiple = true
    p1.schema = {
      examples: ['2036-01-04T19:49:30.224Z', '2036-01-05T19:49:30.224Z'],
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.deepEqual(data.p1, ['2036-01-04T19:49:30.224Z', '2036-01-05T19:49:30.224Z'])
  })
})

test.group('JSON DomainEntity Generator: time', (group) => {
  let e1: DomainEntity

  group.each.setup(() => {
    const ns = new DataDomain()
    const d1 = ns.addModel({ key: 'd1', info: { name: 'd1' } })
    e1 = d1.addEntity({ key: 'e1', info: { name: 'e1' } })
  })

  test('generates an example', ({ assert }) => {
    e1.addProperty({ key: 'p1', type: 'time', info: { name: 'p1' } })
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.p1, 'string', 'has the string value')
    assert.match(data.p1, timeRe)
  })

  test('generates an example with multiple property', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'time', info: { name: 'p1' } })
    p1.multiple = true
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.p1, 'array', 'has the array value')
    assert.lengthOf(data.p1, 1, 'has a single value')
    assert.match(data.p1[0], timeRe)
  })

  test('returns the default value', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'time', info: { name: 'p1' } })
    p1.schema = {
      defaultValue: { value: '2080-02-04T09:39:13', type: 'literal' },
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.equal(data.p1, '2080-02-04T09:39:13')
  })

  test('returns the default value as array with multiple property', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'time', info: { name: 'p1' } })
    p1.multiple = true
    p1.schema = {
      defaultValue: { value: '2080-02-04T09:39:13', type: 'literal' },
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.deepEqual(data.p1, ['2080-02-04T09:39:13'])
  })

  test('returns the example value', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'time', info: { name: 'p1' } })
    p1.schema = {
      examples: ['2080-02-04T09:39:13', '2080-02-05T09:39:13'],
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.equal(data.p1, '2080-02-04T09:39:13')
  })

  test('returns the default value as array with multiple property', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'time', info: { name: 'p1' } })
    p1.multiple = true
    p1.schema = {
      examples: ['2080-02-04T09:39:13', '2080-02-05T09:39:13'],
    }
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.deepEqual(data.p1, ['2080-02-04T09:39:13', '2080-02-05T09:39:13'])
  })
})

test.group('JSON DomainEntity Generator: boolean', (group) => {
  let e1: DomainEntity

  group.each.setup(() => {
    const ns = new DataDomain()
    const d1 = ns.addModel({ key: 'd1', info: { name: 'd1' } })
    e1 = d1.addEntity({ key: 'e1', info: { name: 'e1' } })
  })

  test('generates an example', ({ assert }) => {
    e1.addProperty({ key: 'p1', type: 'boolean', info: { name: 'p1' } })
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.p1, 'boolean', 'has the boolean value')
  })

  test('generates an example with multiple property', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'boolean', info: { name: 'p1' } })
    p1.multiple = true
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.p1, 'array', 'has the array value')
    assert.lengthOf(data.p1, 1, 'has a single value')
    assert.typeOf(data.p1[0], 'boolean', 'has the final value type')
  })
})

// test.group('JSON DomainEntity Generator: nil', (group) => {
//   let e1: DomainEntity

//   group.each.setup(() => {
//     const ns = new DataDomain()
//     const d1 = ns.addModel({ key: 'd1', info: { name: 'd1' } })
//     e1 = d1.addEntity({ key: 'e1', info: { name: 'e1' } })
//   })

//   test('generates an example', ({ assert }) => {
//     e1.addTypedProperty('nil', 'p1')
//     const result = e1.toExample(mime) as string
//     assert.typeOf(result, 'string', 'result is a string')
//     const data = JSON.parse(result)
//     assert.typeOf(data.p1, 'null', 'has the null value')
//   })

//   test('generates an example with multiple property', ({ assert }) => {
//     const p1 = e1.addTypedProperty('nil', 'p1')
//     p1.multiple = true
//     const result = e1.toExample(mime) as string
//     assert.typeOf(result, 'string', 'result is a string')
//     const data = JSON.parse(result)
//     assert.typeOf(data.p1, 'array', 'has the array value')
//     assert.lengthOf(data.p1, 1, 'has a single value')
//     assert.typeOf(data.p1[0], 'null', 'has the final value type')
//   })
// })

test.group('JSON DomainEntity Generator: binary', (group) => {
  let e1: DomainEntity

  group.each.setup(() => {
    const ns = new DataDomain()
    const d1 = ns.addModel({ key: 'd1', info: { name: 'd1' } })
    e1 = d1.addEntity({ key: 'e1', info: { name: 'e1' } })
  })

  test('does not generate an example for a file', ({ assert }) => {
    e1.addProperty({ key: 'p1', type: 'binary' })
    const result = e1.toExample(mime) as string
    assert.equal(result, '{}')
  })

  // this could potentially generate a value
  test('does not generate an example for base64 binary format', ({ assert }) => {
    const p1 = e1.addProperty({ key: 'p1', type: 'binary' })
    p1.getWebBinding().format = 'base64'
    const result = e1.toExample(mime) as string
    assert.equal(result, '{}')
  })
})

test.group('JSON DomainEntity Generator: associations', (group) => {
  let n1: DataDomain
  let d1: DomainModel
  let e1: DomainEntity
  let e2: DomainEntity

  group.each.setup(() => {
    n1 = new DataDomain()
    d1 = n1.addModel({ key: 'd1', info: { name: 'd1' } })
    e1 = d1.addEntity({ key: 'e1', info: { name: 'e1' } })
    e2 = d1.addEntity({ key: 'e2', info: { name: 'e2' } })
  })

  test('creates a property from an association', ({ assert }) => {
    e2.addProperty({ type: 'number', key: 'n1', info: { name: 'n1' } })
    e1.addAssociation({ key: e2.key }, { key: 'a1', info: { name: 'a1' } })

    const result = e1.toExample(mime) as string

    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.a1, 'object', 'has the association property')
    assert.typeOf(data.a1.n1, 'number', 'has the association properties')
  })

  test('creates a deep association', ({ assert }) => {
    const e3 = d1.addEntity({ key: 'e3', info: { name: 'e3' } })
    e3.addProperty({ type: 'number', key: 'i1', info: { name: 'i1' } })
    e2.addProperty({ type: 'number', key: 'n1', info: { name: 'n1' } })
    e1.addAssociation({ key: e2.key }, { key: 'a1', info: { name: 'a1' } })
    e2.addAssociation({ key: e3.key }, { key: 'a2', info: { name: 'a2' } })

    const result = e1.toExample(mime) as string

    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.a1, 'object', 'has the association property')
    assert.typeOf(data.a1.n1, 'number', 'has the association properties')
    assert.typeOf(data.a1.a2, 'object', 'has a deep association')
    assert.typeOf(data.a1.a2.i1, 'number', 'has a deep association property')
  })

  test('does not make associations to self (recursive #1)', ({ assert }) => {
    e1.addAssociation({ key: e1.key }, { key: 'a1' })
    const result = e1.toExample(mime) as string
    assert.equal(result, '{}')
  })

  test('does not make recursive associations (recursive #2)', ({ assert }) => {
    const e3 = d1.addEntity({ key: 'e3', info: { name: 'e3' } })
    e3.addProperty({ type: 'number', key: 'i1', info: { name: 'i1' } })
    e2.addProperty({ type: 'number', key: 'n1', info: { name: 'n1' } })
    e1.addAssociation({ key: e2.key }, { key: 'a1', info: { name: 'a1' } })
    e2.addAssociation({ key: e3.key }, { key: 'a2', info: { name: 'a2' } })
    e3.addAssociation({ key: e1.key }, { key: 'a3', info: { name: 'a3' } })

    const result = e1.toExample(mime) as string

    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.a1, 'object', 'has the association property')
    assert.typeOf(data.a1.n1, 'number', 'has the association properties')
    assert.typeOf(data.a1.a2, 'object', 'has a deep association')
    assert.typeOf(data.a1.a2.i1, 'number', 'has a deep association property')
    assert.isUndefined(data.a1.a2.a3, 'has no recursive property')
  })

  test('returns the default union result', ({ assert }) => {
    const e3 = d1.addEntity({ key: 'e3', info: { name: 'e3' } })
    e3.addProperty({ type: 'number', key: 'i1', info: { name: 'i1' } })
    e2.addProperty({ type: 'string', key: 's1', info: { name: 's1' } })
    const a1 = e1.addAssociation({ key: e2.key }, { key: 'a1', info: { name: 'a1' } })
    a1.addTarget(e3.key)

    const result = e1.toExample(mime) as string

    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.a1, 'object', 'has the association property')
    assert.typeOf(data.a1.s1, 'string', 'has the association properties')
  })

  test('returns the default union result with multiple', ({ assert }) => {
    const e3 = d1.addEntity({ key: 'e3', info: { name: 'e3' } })
    e3.addProperty({ type: 'number', key: 'i1', info: { name: 'i1' } })
    e2.addProperty({ type: 'string', key: 's1', info: { name: 's1' } })
    const a1 = e1.addAssociation({ key: e2.key }, { key: 'a1', info: { name: 'a1' } })
    a1.addTarget(e3.key)
    a1.multiple = true

    const result = e1.toExample(mime) as string

    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.a1, 'array', 'has the association property as an array')
    const [prop] = data.a1
    assert.typeOf(prop, 'object', 'has the item in the array')
    assert.typeOf(prop.s1, 'string', 'has the association properties')
  })

  test('returns the schema for anyOf union', ({ assert }) => {
    const e3 = d1.addEntity({ key: 'e3', info: { name: 'e3' } })

    e2.addProperty({ type: 'string', key: 's1', info: { name: 's1' } })
    e3.addProperty({ type: 'number', key: 'i1', info: { name: 'i1' } })

    const a1 = e1.addAssociation({ key: e2.key }, { key: 'a1', info: { name: 'a1' } })
    a1.addTarget(e3.key)
    a1.schema = { unionType: 'anyOf' }

    const result = e1.toExample(mime) as string

    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.a1, 'object', 'has the association property')
    assert.typeOf(data.a1.s1, 'string', 'has the association properties')
    assert.isUndefined(data.a1.i1, 'has no other association target')
  })

  test('returns the schema for allOf union', ({ assert }) => {
    const e3 = d1.addEntity({ key: 'e3', info: { name: 'e3' } })
    e3.addProperty({ type: 'number', key: 'i1', info: { name: 'i1' } })
    e2.addProperty({ type: 'string', key: 's1', info: { name: 's1' } })
    const a1 = e1.addAssociation({ key: e2.key }, { key: 'a1', info: { name: 'a1' } })
    a1.addTarget(e3.key)
    a1.schema = { unionType: 'allOf' }

    const result = e1.toExample(mime) as string

    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.a1, 'object', 'has the association property')
    assert.typeOf(data.a1.s1, 'string', 'has the association properties')
    assert.typeOf(data.a1.i1, 'number', 'has all association targets')
  })

  test('returns the schema for oneOf union', ({ assert }) => {
    const e3 = d1.addEntity({ key: 'e3', info: { name: 'e3' } })

    e2.addProperty({ type: 'string', key: 's1', info: { name: 's1' } })
    e3.addProperty({ type: 'number', key: 'i1', info: { name: 'i1' } })

    const a1 = e1.addAssociation({ key: e2.key }, { key: 'a1', info: { name: 'a1' } })
    a1.addTarget(e3.key)

    a1.schema = { unionType: 'oneOf' }

    const result = e1.toExample(mime) as string

    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.a1, 'object', 'has the association property')
    assert.typeOf(data.a1.s1, 'string', 'has the association properties')
    assert.isUndefined(data.a1.i1, 'has no other association target')
  })

  test('returns the schema for oneOf union with selected shape', ({ assert }) => {
    const e3 = d1.addEntity({ key: 'e3', info: { name: 'e3' } })

    e2.addProperty({ type: 'string', key: 's1', info: { name: 's1' } })
    e3.addProperty({ type: 'number', key: 'i1', info: { name: 'i1' } })

    const a1 = e1.addAssociation({ key: e2.key }, { key: 'a1', info: { name: 'a1' } })
    a1.addTarget(e3.key)
    a1.schema = { unionType: 'oneOf' }

    const result = e1.toExample(mime) as string

    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.a1, 'object', 'has the association property')
    assert.typeOf(data.a1.s1, 'string', 'has the association properties')
    assert.isUndefined(data.a1.i1, 'has no other association target')
  })

  test('creates a property from a foreign association', ({ assert }) => {
    const n2 = new DataDomain()
    const d2 = n2.addModel({ key: 'd2', info: { name: 'd2' } })
    const e3 = d2.addEntity({ key: 'e2', info: { name: 'e2' } })
    e3.addProperty({ type: 'number', key: 'n1', info: { name: 'n1' } })
    n2.info.version = '1.0.0'
    n1.registerForeignDomain(n2)
    e1.addAssociation({ key: e3.key, domain: n2.key }, { key: 'a3', info: { name: 'a3' } })
    const result = e1.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.a3, 'object', 'has the association property')
    assert.typeOf(data.a3.n1, 'number', 'has the association properties')
  })
})

test.group('JSON DomainEntity Generator: parents', (group) => {
  let d1: DomainModel
  let e1: DomainEntity
  let e2: DomainEntity

  group.each.setup(() => {
    const ns = new DataDomain()
    d1 = ns.addModel({ key: 'd1', info: { name: 'd1' } })
    e1 = d1.addEntity({ key: 'e1', info: { name: 'e1' } })
    e2 = d1.addEntity({ key: 'e2', info: { name: 'e2' } })
  })

  test('inherits parent properties', ({ assert }) => {
    e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    e2.addParent(e1.key)
    const result = e2.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.p1, 'number', 'has the parent property')
  })

  test('inherits parent properties and combines with own', ({ assert }) => {
    e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    e2.addProperty({ key: 'p2', info: { name: 'p2' } })
    e2.addParent(e1.key)

    const result = e2.toExample(mime) as string
    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.p1, 'number', 'has the parent property')
    assert.typeOf(data.p2, 'string', 'has own property')
  })

  test('inherits parent properties and combines with own association', ({ assert }) => {
    e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    e2.addProperty({ key: 'p2', info: { name: 'p2' } })
    e2.addParent(e1.key)
    const e3 = d1.addEntity({ key: 'e3', info: { name: 'e3' } })
    e3.addProperty({ type: 'string', key: 'p3', info: { name: 'p3' } })
    e2.addAssociation({ key: e3.key }, { key: 'a1', info: { name: 'a1' } })

    const result = e2.toExample(mime) as string

    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.p1, 'number', 'has the parent property')
    assert.typeOf(data.p2, 'string', 'has own property')
    assert.typeOf(data.a1, 'object', 'has own association')
    assert.typeOf(data.a1.p3, 'string', 'has association property')
  })

  test('inherits parent properties with parent association', ({ assert }) => {
    e1.addProperty({ key: 'p1', type: 'number', info: { name: 'p1' } })
    e2.addProperty({ key: 'p2', info: { name: 'p2' } })
    e2.addParent(e1.key)
    const e3 = d1.addEntity({ key: 'e3', info: { name: 'e3' } })
    e3.addProperty({ type: 'string', key: 'p3', info: { name: 'p3' } })
    e1.addAssociation({ key: e3.key }, { key: 'a1', info: { name: 'a1' } })

    const result = e2.toExample(mime) as string

    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.p1, 'number', 'has the parent property')
    assert.typeOf(data.p2, 'string', 'has own property')
    assert.typeOf(data.a1, 'object', 'has own association')
    assert.typeOf(data.a1.p3, 'string', 'has association property')
  })

  test('shadows properties', ({ assert }) => {
    e1.addProperty({ type: 'number', info: { name: 'p1' } })
    e2.addProperty({ info: { name: 'p1' } })
    e2.addParent(e1.key)
    const result = e2.toExample(mime) as string

    assert.typeOf(result, 'string', 'result is a string')
    const data = JSON.parse(result)
    assert.typeOf(data.p1, 'string', 'overwrites parent property')
  })
})
