import { test } from '@japa/runner'
import { DOMParser } from '@xmldom/xmldom'
import { ApiSchemaGenerator } from '../../../src/amf/ApiSchemaGenerator.js'
import { AmfLoader } from './AmfLoader.js'
import { IAmfDocument } from '../../../src/amf/definitions/Amf.js'
import { IApiUnionShape } from '../../../src/amf/definitions/Shapes.js'

const loader = new AmfLoader()
const jsonMime = 'application/json'
const xmlMime = 'application/xml'

test.group(`union values / ${jsonMime}`, (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph('schema-api')
  })
  test('generates schema for a scalar union', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarUnion')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime, {
      renderMocked: true,
    })
    assert.typeOf(result, 'string', 'is a string')
    assert.isNotEmpty(result, 'the result has auto-generated value')
  })

  test('generates schema for a scalar union with a default number value', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarUnionDefaultNumber')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime)
    assert.typeOf(result, 'number', 'is a number')
    assert.strictEqual(result, 123456, 'the result is set')
  })

  test('generates schema for a scalar union with a default string value', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarUnionDefaultString')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime)
    assert.typeOf(result, 'string', 'is a string')
    assert.strictEqual(result, 'abc123', 'the result is set')
  })

  test('generates schema for a scalar union with a default boolean value (true)', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarUnionDefaultBoolean')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime)
    assert.typeOf(result, 'boolean', 'is a boolean')
    assert.strictEqual(result, true, 'the result is set')
  })

  test('generates schema for a scalar union with a default boolean value (false)', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarUnionDefaultBoolean2')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime)
    assert.strictEqual(result, false, 'the result is set')
  })

  test('ignores implicit not-required unions (union with nil)', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarUnionDefaultNil')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime)
    assert.isUndefined(result)
  })

  test('generates schema for required union an example value (number)', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarUnionExampleNumber')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime, {
      renderExamples: true,
    })
    assert.strictEqual(result, 987654, 'the result is a number')
  })

  test('generates schema for required union an example value (string)', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarUnionExampleString')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime, {
      renderExamples: true,
    })
    assert.strictEqual(result, '098poi', 'the result is a string')
  })

  test('generates a string value for scalar-object union', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarObjectUnion')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime, {
      renderMocked: true,
    })
    assert.typeOf(result, 'string', 'the result is a string')
    assert.isNotEmpty(result, 'has a value')
  })

  test('generates schema for scalar-object union with an example', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarObjectUnionExample')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime, {
      renderExamples: true,
    })
    assert.typeOf(result, 'string', 'the result is a string')
    const parsed = JSON.parse(String(result))
    assert.strictEqual(parsed.id, '128a654bc54d898e43f', 'example.id is set')
    assert.strictEqual(parsed.name, 'Pawel Uchida-Psztyc', 'example.name is set')
    assert.strictEqual(parsed.age, 30, 'example.age is set')
    assert.strictEqual(parsed.sex, 'male', 'example.sex is set')
    assert.strictEqual(parsed.newsletter, false, 'example.newsletter is set')
    assert.strictEqual(parsed.tosAccepted, true, 'example.tosAccepted is set')
  })

  test('generates schema for objects union', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectUnions')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime)
    assert.typeOf(result, 'string', 'the result is a string')
    const parsed = JSON.parse(String(result))
    assert.typeOf(parsed.name, 'string', 'schema.name is set')
    assert.isUndefined(parsed.age, 'optional item is not set (age)')
    assert.strictEqual(parsed.sex, 'male', 'schema.sex is set')
    assert.isUndefined(parsed.newsletter, 'optional item is not set (newsletter)')
    assert.typeOf(parsed.tosAccepted, 'boolean', 'schema.tosAccepted is set')
  })

  test('generates schema for a selected union', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectUnions') as IApiUnionShape
    const id = shape.anyOf[1].id
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime, {
      selectedUnions: [id],
      renderMocked: true,
    })
    assert.typeOf(result, 'string', 'the result is a string')
    const parsed = JSON.parse(String(result))
    assert.typeOf(parsed.name, 'string', 'has parent name')
    assert.typeOf(parsed.age, 'number', 'has child age')
    assert.strictEqual(parsed.sex, 'male', 'has parent sex')
    assert.isUndefined(parsed.newsletter, 'has no optional parent newsletter')
    assert.typeOf(parsed.tosAccepted, 'boolean', 'has parent tosAccepted')
    assert.match(parsed.addedProperty, /^[0-9]{2}:[0-9]{2}:[0-9]{2}$/, 'has child addedProperty')
  })

  // examples are not correctly inherited with unions
  test('renders example from an union object', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectUnionWithExample')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime, {
      renderExamples: true,
    })
    assert.typeOf(result, 'string', 'the result is a string')
    const parsed = JSON.parse(String(result))
    assert.strictEqual(parsed.error, true, 'has schema.error')
    assert.strictEqual(parsed.message, 'Error message', 'has schema.message')
    assert.isUndefined(parsed.optional, 'has no optional property')
  }).skip()

  test('does not renders example from an union object when not configured', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectUnionWithExample')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime)
    assert.typeOf(result, 'string', 'the result is a string')
    const parsed = JSON.parse(String(result))
    assert.typeOf(parsed.error, 'boolean', 'has default schema.error')
    assert.typeOf(parsed.message, 'string', 'has generated schema.message')
    assert.isUndefined(parsed.optional, 'has no optional property')
  })

  test('marks scalar | nil union as optional', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectScalarUnionNill')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime)
    assert.equal(result, '{}', 'is a string')
  })

  // I am not sure how this should work.
  // In RAML spec it says that scalar + nil makes the scalar optional.
  // Should it be the same for an object?
  test('marks object | nil union as optional', async ({ assert }) => {
    const shape = loader.getShape(model, 'UnionNill')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime)
    assert.equal(result, '{}', 'is a string')
  }).skip()
})

test.group(`union values / ${xmlMime}`, (group) => {
  let model: IAmfDocument

  group.setup(async () => {
    model = await loader.getGraph('schema-api')
  })
  test('generates schema for a scalar union', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarUnion')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime, {
      renderMocked: true,
    })
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    const root = schema.getElementsByTagName('ScalarUnion')[0]
    assert.ok(root, 'has the root node')
    assert.isNotEmpty(root.textContent!.trim(), 'the root node is empty')
  })

  test('generates schema for a scalar union with a default number value', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarUnionDefaultNumber')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime)
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    const root = schema.getElementsByTagName('ScalarUnionDefaultNumber')[0]
    assert.ok(root, 'has the root node')
    assert.equal(root.textContent!.trim(), '123456', 'the root node has a number value')
  })

  test('generates schema for a scalar union with a default string value', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarUnionDefaultString')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime)
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    const root = schema.getElementsByTagName('ScalarUnionDefaultString')[0]
    assert.ok(root, 'has the root node')
    assert.equal(root.textContent!.trim(), 'abc123', 'the root node has a string value')
  })

  test('generates schema for a scalar union with a default boolean value', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarUnionDefaultBoolean')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime)
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    const root = schema.getElementsByTagName('ScalarUnionDefaultBoolean')[0]
    assert.ok(root, 'has the root node')
    assert.equal(root.textContent!.trim(), 'true', 'the root node has a boolean value')
  })

  test('ignores implicit not-required unions (union with nil)', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarUnionDefaultNil')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime)
    assert.isUndefined(result)
  })

  test('generates schema for required union an example value (number)', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarUnionExampleNumber')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime)
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    const root = schema.getElementsByTagName('ScalarUnionExampleNumber')[0]
    assert.ok(root, 'has the root node')
    assert.equal(root.textContent!.trim(), '987654', 'the root node has the example value')
  })

  test('generates schema for required union an example value (string)', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarUnionExampleString')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime)
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    const root = schema.getElementsByTagName('ScalarUnionExampleString')[0]
    assert.ok(root, 'has the root node')
    assert.equal(root.textContent!.trim(), '098poi', 'the root node has the example value')
  })

  test('generates empty string for scalar-object union', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarObjectUnion')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime)
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    const root = schema.getElementsByTagName('ScalarObjectUnion')[0]
    assert.ok(root, 'has the root node')
    assert.equal(root.textContent!.trim(), '', 'the root node is empty')
  })

  test('generates mocked string for scalar-object union', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarObjectUnion')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime, {
      renderMocked: true,
    })
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    const root = schema.getElementsByTagName('ScalarObjectUnion')[0]
    assert.ok(root, 'has the root node')
    assert.isNotEmpty(root.textContent!.trim(), 'the root node is not empty')
  })

  test('generates schema for scalar-object union with an example', async ({ assert }) => {
    const shape = loader.getShape(model, 'ScalarObjectUnionExample')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime)
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    const root = schema.getElementsByTagName('ScalarObjectUnionExample')[0]
    assert.ok(root, 'has the root node')
    assert.strictEqual(
      root.getElementsByTagName('id')[0].textContent!.trim(),
      '128a654bc54d898e43f',
      'example.id is set'
    )
    assert.strictEqual(
      root.getElementsByTagName('name')[0].textContent!.trim(),
      'Pawel Uchida-Psztyc',
      'example.name is set'
    )
    assert.strictEqual(root.getElementsByTagName('age')[0].textContent!.trim(), '30', 'example.age is set')
    assert.strictEqual(root.getElementsByTagName('sex')[0].textContent!.trim(), 'male', 'example.sex is set')
    assert.strictEqual(
      root.getElementsByTagName('newsletter')[0].textContent!.trim(),
      'false',
      'example.newsletter is set'
    )
    assert.strictEqual(
      root.getElementsByTagName('tosAccepted')[0].textContent!.trim(),
      'true',
      'example.tosAccepted is set'
    )
  })

  test('generates schema for objects union', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectUnions')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime, {
      renderMocked: true,
    })
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    const root = schema.getElementsByTagName('ObjectUnions')[0]
    assert.ok(root, 'has the root node')
    assert.isNotEmpty(root.getElementsByTagName('id')[0].textContent!.trim(), 'example.id is set')
    assert.isNotEmpty(root.getElementsByTagName('name')[0].textContent!.trim(), 'example.name is set')
    assert.notOk(root.getElementsByTagName('age')[0], 'optional item is not set (age)')
    assert.strictEqual(root.getElementsByTagName('sex')[0].textContent!.trim(), 'male', 'example.sex is set')
    assert.notOk(root.getElementsByTagName('newsletter')[0], 'optional item is not set (newsletter)')
    assert.strictEqual(
      root.getElementsByTagName('tosAccepted')[0].textContent!.trim(),
      'false',
      'example.tosAccepted is set'
    )
  })

  test('generates schema for a selected union', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectUnions') as IApiUnionShape
    // const typedUnion = (shape.inherits[0]);
    const id = shape.anyOf[1].id
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime, {
      selectedUnions: [id],
      renderMocked: true,
    })
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    const root = schema.getElementsByTagName('ObjectUnions')[0]
    assert.ok(root, 'has the root node')
    assert.isNotEmpty(root.getElementsByTagName('name')[0].textContent!.trim(), 'has parent name')
    assert.match(root.getElementsByTagName('age')[0].textContent!.trim(), /^\d+$/, 'has child age')
    assert.strictEqual(root.getElementsByTagName('sex')[0].textContent!.trim(), 'male', 'has parent sex')
    assert.notOk(root.getElementsByTagName('newsletter')[0], 'has no optional parent newsletter')
    assert.strictEqual(
      root.getElementsByTagName('tosAccepted')[0].textContent!.trim(),
      'false',
      'has parent tosAccepted'
    )
    const addedProperty = root.getElementsByTagName('addedProperty')[0].textContent!.trim()
    assert.match(addedProperty, /^[0-9]{2}:[0-9]{2}:[0-9]{2}$/, 'has child addedProperty')
  })

  // examples are not correctly inherited with unions
  test('renders example from an union object', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectUnionWithExample')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime, {
      renderExamples: true,
    })
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    const root = schema.getElementsByTagName('ObjectUnionWithExample')[0]
    assert.ok(root, 'has the root node')
    assert.strictEqual(root.getElementsByTagName('error')[0].textContent!.trim(), 'true', 'has schema.error')
    assert.strictEqual(
      root.getElementsByTagName('message')[0].textContent!.trim(),
      'Error message',
      'has schema.message'
    )
    assert.notOk(root.getElementsByTagName('optional')[0], 'has no optional property')
  }).skip()

  test('does not renders example from an union object when not configured', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectUnionWithExample')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime, {
      renderMocked: true,
    })
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    const root = schema.getElementsByTagName('ObjectUnionWithExample')[0]
    assert.ok(root, 'has the root node')
    assert.match(root.getElementsByTagName('error')[0].textContent!.trim(), /false|true/, 'has default schema.error')
    assert.isNotEmpty(root.getElementsByTagName('message')[0].textContent!.trim(), 'has default schema.message')
    assert.notOk(root.getElementsByTagName('optional')[0], 'has no optional property')
  })

  test('marks scalar | nil union as optional', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectScalarUnionNill')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime)
    assert.equal(result, '<ObjectScalarUnionNill>\n</ObjectScalarUnionNill>', 'is a string')
  })

  // I am not sure how this should work.
  // In RAML spec it says that scalar + nil makes the scalar optional.
  // Should it be the same for an object?
  test('marks object | nil union as optional', async ({ assert }) => {
    const shape = loader.getShape(model, 'UnionNill')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime)
    assert.equal(result, '<UnionNill></UnionNill>', 'is a string')
  }).skip()
})
