import { test } from '@japa/runner'
import { DOMParser } from '@xmldom/xmldom'
import { AmfNamespace as ns } from '../../../src/amf/definitions/Namespace.js'
import { ApiSchemaGenerator, ISchemaExample } from '../../../src/amf/ApiSchemaGenerator.js'
import { AmfLoader } from './AmfLoader.js'
import { IAmfDocument } from '../../../src/amf/definitions/Amf.js'

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

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

  group.setup(async () => {
    model = await loader.getGraph('schema-api')
  })

  test('generates a schema for a simple object', async ({ assert }) => {
    const shape = loader.getShape(model, 'SimpleObject')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime)
    assert.typeOf(result, 'string', 'is a string')
    const parsed = JSON.parse(String(result))
    assert.typeOf(parsed, 'object', 'represents an object')
    ;['id', 'name', 'sex', 'tosAccepted'].forEach((key) => assert.isDefined(parsed[key], `${key} property is set`))
  })

  test('has only required fields with default values or enums', async ({ assert }) => {
    const shape = loader.getShape(model, 'SimpleObject')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime)
    assert.typeOf(result, 'string', 'is a string')
    const schema = JSON.parse(String(result))

    assert.typeOf(schema.id, 'string', 'sets a string type (id)')
    assert.typeOf(schema.name, 'string', 'sets a string type (name)')
    assert.strictEqual(schema.sex, 'male', 'sets a string type (sex)')
    assert.strictEqual(schema.tosAccepted, false, 'sets a boolean type from examples')
    assert.isUndefined(schema.newsletter, 'sets a boolean type from examples')
    assert.isUndefined(schema.age, 'sets a boolean type from examples')
  })

  test('has default values and examples', async ({ assert }) => {
    const shape = loader.getShape(model, 'SimpleObject')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime, {
      renderExamples: true,
    })
    const schema = JSON.parse(String(result))
    assert.strictEqual(schema.name, 'Pawel Uchida-Psztyc', 'sets a string type (name)')
    assert.strictEqual(schema.sex, 'male', 'sets a string type (sex)')
    assert.strictEqual(schema.tosAccepted, false, 'sets a boolean type from examples')
  })

  test('has default values and examples with optional', async ({ assert }) => {
    const shape = loader.getShape(model, 'SimpleObject')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime, {
      renderOptional: true,
      renderExamples: true,
    })
    const schema = JSON.parse(String(result))

    assert.strictEqual(schema.name, 'Pawel Uchida-Psztyc', 'sets a string type (name)')
    assert.strictEqual(schema.sex, 'male', 'sets a string type (sex)')
    assert.typeOf(schema.tosAccepted, 'boolean', 'sets a boolean type from examples')
    assert.strictEqual(schema.age, 21, 'has the age property')
    assert.typeOf(schema.newsletter, 'boolean', 'has the newsletter property')
  })

  test('gets the parent properties', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectWithParent')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime, {
      renderOptional: true,
      renderExamples: true,
      renderMocked: true,
    })
    const parsed = JSON.parse(String(result))
    assert.strictEqual(parsed.name, 'Pawel Uchida-Psztyc', 'has parent name')
    assert.strictEqual(parsed.age, 25, 'has child age')
    assert.typeOf(parsed.newsletter, 'boolean', 'has 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')
  })

  test('renders object example value', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectWithExample')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime, {
      renderOptional: false,
      renderExamples: true,
    })
    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')
  })

  test('renders object with multiple level parents', async ({ assert }) => {
    const shape = loader.getShape(model, 'ChildLvl2')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime)
    const parsed = JSON.parse(String(result))
    assert.typeOf(parsed.id, 'string', 'has top most property')
    assert.typeOf(parsed.ch1, 'boolean', 'has parent property')
    assert.typeOf(parsed.ch2, 'string', 'has own property')
  })

  test('generates the example', async ({ assert }) => {
    const shape = loader.getShape(model, 'SimpleObject')
    const result = ApiSchemaGenerator.asExample(shape, jsonMime) as ISchemaExample
    assert.typeOf(result, 'object', 'returns an object')
    assert.isTrue(result.strict, 'has the strict property')
    assert.deepEqual(result.types, [ns.aml.vocabularies.apiContract.Example], 'has the types property')
    assert.equal(result.mediaType, jsonMime, 'has the mediaType property')
    assert.typeOf(result.renderValue, 'string', 'has the renderValue')
    const parsed = JSON.parse(result.renderValue as string)
    assert.typeOf(parsed, 'object', 'represents an object')
  })
})

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

  group.setup(async () => {
    model = await loader.getGraph('schema-api')
  })

  test('has only required fields with default values or enums', async ({ assert }) => {
    const shape = loader.getShape(model, 'SimpleObject')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime, {
      renderMocked: true,
    })
    assert.typeOf(result, 'string', 'is a string')
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    assert.ok(schema.getElementsByTagName('id')[0], 'sets a string type (id)')
    assert.ok(schema.getElementsByTagName('name')[0], 'has the name')
    assert.isNotEmpty(schema.getElementsByTagName('name')[0].textContent!.trim(), 'sets a string type (name)')
    assert.strictEqual(schema.getElementsByTagName('sex')[0].textContent!.trim(), 'male', 'sets a string type (sex)')
    assert.strictEqual(
      schema.getElementsByTagName('tosAccepted')[0].textContent!.trim(),
      'false',
      'sets a boolean type from examples'
    )
    assert.isUndefined(schema.getElementsByTagName('newsletter')[0], 'has no optional value (newsletter)')
    assert.isUndefined(schema.getElementsByTagName('age')[0], 'has no optional value (age)')
  })

  test('has default values and examples', async ({ assert }) => {
    const shape = loader.getShape(model, 'SimpleObject')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime, {
      renderExamples: true,
    })
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    assert.strictEqual(
      schema.getElementsByTagName('name')[0].textContent!.trim(),
      'Pawel Uchida-Psztyc',
      'sets a string type (name)'
    )
    assert.strictEqual(schema.getElementsByTagName('sex')[0].textContent!.trim(), 'male', 'sets a string type (sex)')
    assert.strictEqual(
      schema.getElementsByTagName('tosAccepted')[0].textContent!.trim(),
      'false',
      'sets a boolean type from examples'
    )
    assert.isUndefined(schema.getElementsByTagName('newsletter')[0], 'has no optional value (newsletter)')
    assert.isUndefined(schema.getElementsByTagName('age')[0], 'has no optional value (age)')
  })

  test('has default values and examples with optional', async ({ assert }) => {
    const shape = loader.getShape(model, 'SimpleObject')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime, {
      renderOptional: true,
      renderExamples: true,
    })
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    assert.strictEqual(
      schema.getElementsByTagName('name')[0].textContent!.trim(),
      'Pawel Uchida-Psztyc',
      'sets a string type (name)'
    )
    assert.strictEqual(schema.getElementsByTagName('sex')[0].textContent!.trim(), 'male', 'sets a string type (sex)')
    assert.strictEqual(
      schema.getElementsByTagName('tosAccepted')[0].textContent!.trim(),
      'false',
      'sets a boolean type from examples'
    )
    assert.strictEqual(schema.getElementsByTagName('age')[0].textContent!.trim(), '21', 'has the age property')
    assert.match(
      schema.getElementsByTagName('newsletter')[0].textContent!.trim(),
      /false|true/,
      'has the newsletter property'
    )
  })

  test('gets the parent properties', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectWithParent')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime, {
      renderOptional: true,
      renderExamples: true,
      renderMocked: true,
    })
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    assert.strictEqual(
      schema.getElementsByTagName('name')[0].textContent!.trim(),
      'Pawel Uchida-Psztyc',
      'sets a string type (name)'
    )
    assert.strictEqual(schema.getElementsByTagName('sex')[0].textContent!.trim(), 'male', 'sets a string type (sex)')
    assert.strictEqual(
      schema.getElementsByTagName('tosAccepted')[0].textContent!.trim(),
      'false',
      'sets a boolean type from examples'
    )
    assert.strictEqual(schema.getElementsByTagName('age')[0].textContent!.trim(), '25', 'has the age property')
    assert.match(
      schema.getElementsByTagName('newsletter')[0].textContent!.trim(),
      /false|true/,
      'has the newsletter property'
    )
    const addedProperty = schema.getElementsByTagName('addedProperty')[0].textContent!.trim()
    assert.match(addedProperty, /^[0-9]{2}:[0-9]{2}:[0-9]{2}$/, 'has child addedProperty')
  })

  test('renders object example value', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectWithExample')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime, {
      renderOptional: false,
      renderExamples: true,
    })
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    const root = schema.getElementsByTagName('ObjectWithExample')[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')
  })

  test('renders object with multiple level parents', async ({ assert }) => {
    const shape = loader.getShape(model, 'ChildLvl2')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime, {
      renderMocked: true,
    })
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    const root = schema.getElementsByTagName('ChildLvl2')[0]
    assert.ok(root, 'has the root node')
    assert.isNotEmpty(root.getElementsByTagName('ch2')[0].textContent!.trim(), 'has own property')
    assert.match(root.getElementsByTagName('ch1')[0].textContent!.trim(), /false|true/, 'has parent property')
    assert.isNotEmpty(root.getElementsByTagName('id')[0].textContent!.trim(), 'has top most property')
  })

  test('generates the example', async ({ assert }) => {
    const shape = loader.getShape(model, 'SimpleObject')
    const result = ApiSchemaGenerator.asExample(shape, xmlMime) as ISchemaExample
    assert.typeOf(result, 'object', 'returns an object')
    assert.isTrue(result.strict, 'has the strict property')
    assert.deepEqual(result.types, [ns.aml.vocabularies.apiContract.Example], 'has the types property')
    assert.equal(result.mediaType, xmlMime, 'has the mediaType property')
    assert.typeOf(result.renderValue, 'string', 'has the renderValue')
    const parser = new DOMParser()
    const schema = parser.parseFromString(result.renderValue as string, xmlMime)
    assert.strictEqual(
      schema.getElementsByTagName('tosAccepted')[0].textContent!.trim(),
      'false',
      'sets a boolean type from examples'
    )
  })
})
