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(`array values / ${jsonMime}`, (group) => {
  let model: IAmfDocument

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

  test('generates a valid schema', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectWithArray')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime)
    assert.typeOf(result, 'string', 'is a string')
    const parsed = JSON.parse(String(result))
    assert.typeOf(parsed, 'object', 'result is an object')
  })

  test('has only required fields', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectWithArray')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime)
    assert.typeOf(result, 'string', 'is a string')
    const parsed = JSON.parse(String(result))
    assert.typeOf(parsed.tags, 'array', 'tags is set')
    assert.typeOf(parsed.exampleValue, 'array', 'exampleValue is set')
    assert.typeOf(parsed.examplesValue, 'array', 'examplesValue is set')
    assert.typeOf(parsed.defaultValue, 'array', 'defaultValue is set')
  })

  test('has only default values', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectWithArray')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime)
    assert.typeOf(result, 'string', 'is a string')
    const parsed = JSON.parse(String(result))
    assert.typeOf(parsed.tags, 'array', 'tags is set')
    assert.typeOf(parsed.tags[0], 'string', 'tags have correct type')
    assert.typeOf(parsed.exampleValue, 'array', 'exampleValue is set')
    assert.typeOf(parsed.exampleValue[0], 'number', 'exampleValue is set')
    assert.deepEqual(parsed.defaultValue, ['A tag'], 'defaultValue is set')
  })

  test('has default values and examples', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectWithArray')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime, {
      renderExamples: true,
    })
    assert.typeOf(result, 'string', 'is a string')
    const parsed = JSON.parse(String(result))
    assert.typeOf(parsed.tags, 'array', 'tags is set')
    assert.typeOf(parsed.tags[0], 'string', 'tags have correct type')
    assert.deepEqual(parsed.exampleValue, [123], 'exampleValue is set')
    assert.deepEqual(parsed.examplesValue, ['value 1'], 'examplesValue is set')
    assert.deepEqual(parsed.defaultValue, ['A tag'], 'defaultValue is set')
  })

  test('processes object items', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectWithArrayObject')
    const result = ApiSchemaGenerator.asSchema(shape, jsonMime, {
      renderExamples: true,
      renderMocked: true,
    })
    assert.typeOf(result, 'string', 'is a string')
    const parsed = JSON.parse(String(result))
    assert.typeOf(parsed, 'array', 'result is an array')
    const [schema] = parsed
    assert.isNotEmpty(schema.id, 'sets a string type (id)')
    assert.typeOf(schema.id, 'string', 'sets a string type (id)')
    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('generates the example', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectWithArray')
    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', 'result is an object')
  })
})

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

  group.setup(async () => {
    model = await loader.getGraph('schema-api')
  })
  test('has only required fields', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectWithArray')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime, {
      renderMocked: true,
    })
    const parser = new DOMParser()
    const schema = parser.parseFromString(String(result), xmlMime)

    const tags = schema.getElementsByTagName('tags')[0]
    assert.ok(tags, 'has array element')
    assert.isNotEmpty(tags.textContent!.trim(), 'has auto-generated array item (tags)')

    const exampleValue = schema.getElementsByTagName('exampleValue')[0]
    assert.ok(exampleValue, 'has exampleValue array')
    // this is a required field with integer value. This automatically adds number value to the schema.
    assert.match(exampleValue.textContent!.trim(), /^\d+$/, 'has default generated value (exampleValue)')

    const examplesValue = schema.getElementsByTagName('examplesValue')[0]
    assert.ok(examplesValue, 'has examplesValue array')
    assert.isNotEmpty(examplesValue.textContent!.trim(), 'has generated array item (examplesValue)')

    const defaultValue = schema.getElementsByTagName('defaultValue')[0]
    assert.ok(defaultValue, 'has defaultValue array')
    assert.strictEqual(defaultValue.textContent!.trim(), 'A tag', 'has a default value (defaultValue)')
  })

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

    const tags = schema.getElementsByTagName('tags')[0]
    assert.ok(tags, 'has tags array')
    assert.isNotEmpty(tags.textContent!.trim(), 'has auto-generated array item (tags)')

    const exampleValue = schema.getElementsByTagName('exampleValue')[0]
    assert.ok(exampleValue, 'has exampleValue array')
    assert.strictEqual(exampleValue.textContent!.trim(), '123', 'has example value (exampleValue)')

    const examplesValue = schema.getElementsByTagName('examplesValue')
    assert.lengthOf(examplesValue, 1, 'has the examplesValue array')
    assert.strictEqual(examplesValue[0].textContent!.trim(), 'value 1', 'has example value (examplesValue[0])')
    // assert.strictEqual(examplesValue[1].textContent!.trim(), 'value 2', 'has example value (examplesValue[1])');

    const defaultValue = schema.getElementsByTagName('defaultValue')[0]
    assert.ok(defaultValue, 'has defaultValue array')
    assert.strictEqual(defaultValue.textContent!.trim(), 'A tag', 'has a default value (defaultValue)')
  })

  test('processes object items without wrapping', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectWithArrayObject')
    const result = ApiSchemaGenerator.asSchema(shape, xmlMime, {
      renderExamples: true,
    })
    const parser = new DOMParser()
    // array items not wrapped comes without parent.
    const wrapped = `<parent-wrapper>${result}</parent-wrapper>`
    const schema = parser.parseFromString(wrapped, xmlMime)

    const parent = schema.getElementsByTagName('ObjectWithArrayObject')[0]
    assert.ok(parent, 'has ObjectWithArrayObject object')

    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('generates the example', async ({ assert }) => {
    const shape = loader.getShape(model, 'ObjectWithArray')
    const result = ApiSchemaGenerator.asExample(shape, xmlMime, {
      renderMocked: true,
    }) 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)

    const tags = schema.getElementsByTagName('tags')
    assert.lengthOf(tags, 1, 'has only one <tags> element')
    assert.isNotEmpty(tags[0].textContent!.trim(), 'has generated array item (tags)')
  })
})
