import { test } from '@japa/runner'
import { AmfLoader } from '../AmfLoader.js'
import { AmfSerializer } from '../../../../src/amf/AmfSerializer.js'
import type { IAmfDocument } from '../../../../src/amf/definitions/Amf.js'
import type {
  IApiArrayShape,
  IApiFileShape,
  IApiNodeShape,
  IApiPropertyShape,
  IApiRecursiveShape,
  IApiScalarNode,
  IApiScalarShape,
  IApiSchemaShape,
  IApiUnionShape,
} from '../../../../src/amf/definitions/Shapes.js'
import { AmfNamespace } from '../../../../src/amf/definitions/Namespace.js'
import { IApiCustomDomainProperty } from '../../../../src/amf/definitions/Base.js'

const loader = new AmfLoader()

test.group('RAML shapes', (group) => {
  let api: IAmfDocument
  let serializer: AmfSerializer
  group.setup(async () => {
    api = await loader.getGraph('amf-helper-api')
    serializer = new AmfSerializer()
    serializer.amf = api
  })

  test('processes an ArrayShape', ({ assert }) => {
    const shape = loader.lookupDeclaredShape(api, 'Arrable')
    const result = serializer.unknownShape(shape) as IApiArrayShape
    assert.equal(result.id, shape['@id'], 'has the id')
    assert.include(result.types, AmfNamespace.aml.vocabularies.shapes.ArrayShape, 'has the ArrayShape type')
    assert.equal(result.name, 'Arrable', 'has the name')
    assert.deepEqual(result.examples, [], 'has empty examples')
    assert.deepEqual(result.values, [], 'has empty values')
    assert.deepEqual(result.inherits, [], 'has empty inherits')
    assert.deepEqual(result.or, [], 'has empty or')
    assert.deepEqual(result.and, [], 'has empty and')
    assert.deepEqual(result.xone, [], 'has empty xone')
    assert.deepEqual(result.customDomainProperties, [], 'has empty customDomainProperties')
    assert.typeOf(result.items, 'object', 'has the items')
    assert.include(result.items!.types, AmfNamespace.w3.shacl.NodeShape, 'items has the NodeShape type')
    assert.typeOf(result.sourceMaps, 'object', 'has source maps')
  })

  test('processes a NodeShape', ({ assert }) => {
    const shape = loader.lookupDeclaredShape(api, 'AppPerson')
    const result = serializer.unknownShape(shape) as IApiNodeShape
    assert.equal(result.id, shape['@id'], 'has the id')
    assert.include(result.types, AmfNamespace.w3.shacl.NodeShape, 'has the NodeShape type')
    assert.equal(result.name, 'AppPerson', 'has the name')
    assert.typeOf(result.description, 'string', 'has the description')
    assert.lengthOf(result.examples, 1, 'has an example')
    assert.include(
      result.examples[0].types,
      AmfNamespace.aml.vocabularies.apiContract.Example,
      'example has the Example type'
    )
    assert.deepEqual(result.values, [], 'has empty values')
    assert.deepEqual(result.inherits, [], 'has empty inherits')
    assert.deepEqual(result.or, [], 'has empty or')
    assert.deepEqual(result.and, [], 'has empty and')
    assert.deepEqual(result.xone, [], 'has empty xone')
    assert.deepEqual(result.customShapeProperties, [], 'has empty customShapeProperties')
    assert.deepEqual(result.customShapePropertyDefinitions, [], 'has empty customShapePropertyDefinitions')
    assert.deepEqual(result.dependencies, [], 'has empty dependencies')
    assert.deepEqual(result.customDomainProperties, [], 'has empty customDomainProperties')
    assert.isFalse(result.closed, 'has the closed')
    assert.typeOf(result.properties, 'array', 'has the properties')
    assert.isNotEmpty(result.properties, 'the properties is not empty')
    assert.typeOf(result.sourceMaps, 'object', 'has source maps')
  })

  test('processes an UnionShape', ({ assert }) => {
    const shape = loader.lookupDeclaredShape(api, 'Unionable')
    const result = serializer.unknownShape(shape) as IApiUnionShape
    assert.equal(result.id, shape['@id'], 'has the id')
    assert.include(result.types, AmfNamespace.aml.vocabularies.shapes.UnionShape, 'has the NodeShape type')
    assert.equal(result.name, 'Unionable', 'has the name')
    assert.isUndefined(result.description, 'has no description')
    assert.deepEqual(result.examples, [], 'has no examples')
    assert.deepEqual(result.values, [], 'has empty values')
    assert.deepEqual(result.inherits, [], 'has empty inherits')
    assert.deepEqual(result.or, [], 'has empty or')
    assert.deepEqual(result.and, [], 'has empty and')
    assert.deepEqual(result.xone, [], 'has empty xone')
    assert.deepEqual(result.customDomainProperties, [], 'has empty customDomainProperties')
    assert.typeOf(result.anyOf, 'array', 'has the anyOf')
    assert.lengthOf(result.anyOf, 2, 'has all anyOf')
    assert.typeOf(result.sourceMaps, 'object', 'has source maps')
    const [any1] = result.anyOf
    assert.typeOf(any1.id, 'string', 'has the anyOf definition as Shape')
    assert.typeOf(any1.sourceMaps, 'object', 'has source maps')
  })

  test('processes a ScalarShape', ({ assert }) => {
    const shape = loader.lookupDeclaredShape(api, 'Feature')
    const result = serializer.unknownShape(shape) as IApiScalarShape
    assert.equal(result.id, shape['@id'], 'has the id')
    assert.include(result.types, AmfNamespace.aml.vocabularies.shapes.ScalarShape, 'has the NodeShape type')
    assert.equal(result.name, 'Feature', 'has the name')
    assert.typeOf(result.description, 'string', 'has the description')
    assert.deepEqual(result.examples, [], 'has no examples')
    assert.deepEqual(result.inherits, [], 'has empty inherits')
    assert.deepEqual(result.or, [], 'has empty or')
    assert.deepEqual(result.and, [], 'has empty and')
    assert.deepEqual(result.xone, [], 'has empty xone')
    assert.deepEqual(result.customDomainProperties, [], 'has empty customDomainProperties')
    assert.equal(result.dataType, 'http://www.w3.org/2001/XMLSchema#string', 'has the dataType')
    assert.lengthOf(result.values, 3, 'has the values set')
    assert.typeOf(result.sourceMaps, 'object', 'has source maps')
  })

  test('processes a ScalarShape with enum values', ({ assert }) => {
    const shape = loader.lookupDeclaredShape(api, 'Feature')
    const result = serializer.unknownShape(shape) as IApiScalarShape
    const [v1, v2, v3] = result.values as IApiScalarNode[]
    assert.include(v1.types, AmfNamespace.aml.vocabularies.data.Scalar, 'v1 has the type')
    assert.include(v2.types, AmfNamespace.aml.vocabularies.data.Scalar, 'v2 has the type')
    assert.include(v3.types, AmfNamespace.aml.vocabularies.data.Scalar, 'v3 has the type')
    assert.equal(v1.name, 'scalar_1', 'v1 has the name')
    assert.equal(v2.name, 'scalar_2', 'v2 has the name')
    assert.equal(v3.name, 'scalar_3', 'v3 has the name')
    assert.equal(v1.value, 'A', 'v1 has the value')
    assert.equal(v2.value, 'B', 'v2 has the value')
    assert.equal(v3.value, 'C', 'v3 has the value')
    assert.equal(v1.dataType, AmfNamespace.w3.xmlSchema.string, 'v1 has the dataType')
    assert.equal(v2.dataType, AmfNamespace.w3.xmlSchema.string, 'v2 has the dataType')
    assert.equal(v3.dataType, AmfNamespace.w3.xmlSchema.string, 'v3 has the dataType')
    assert.typeOf(result.sourceMaps, 'object', 'has source maps')
  })

  test('processes a FileShape with enum values', ({ assert }) => {
    const shape = loader.lookupDeclaredShape(api, 'MaFile')
    const result = serializer.unknownShape(shape) as IApiFileShape

    assert.equal(result.id, shape['@id'], 'has the id')
    assert.include(result.types, AmfNamespace.aml.vocabularies.shapes.FileShape, 'has the FileShape type')
    assert.equal(result.name, 'MaFile', 'has the name')
    assert.typeOf(result.description, 'string', 'has the description')
    assert.deepEqual(result.examples, [], 'has no examples')
    assert.deepEqual(result.inherits, [], 'has empty inherits')
    assert.deepEqual(result.or, [], 'has empty or')
    assert.deepEqual(result.and, [], 'has empty and')
    assert.deepEqual(result.xone, [], 'has empty xone')
    assert.deepEqual(result.values, [], 'has empty values')
    assert.deepEqual(result.customDomainProperties, [], 'has empty customDomainProperties')
    assert.deepEqual(result.fileTypes, ['image/png', 'image/jpeg', 'image/jpg', 'image/gif'], 'has the fileTypes')
    assert.equal(result.minLength, 1, 'has the minLength set')
    assert.equal(result.maxLength, 10, 'has the maxLength set')
    assert.typeOf(result.sourceMaps, 'object', 'has source maps')
  })

  test('processes a RecursiveShape', ({ assert }) => {
    const shape = loader.lookupDeclaredShape(api, 'RecursiveShape')
    const result = serializer.unknownShape(shape) as IApiNodeShape
    // AMF moved the recursive shape to properties. When they reverse that then restore the previous "item".
    const item = ((result.properties[1].range as IApiNodeShape).properties[1] as IApiPropertyShape)
      .range as IApiRecursiveShape
    // const item = (result.properties[1].range) as IApiRecursiveShape;
    assert.typeOf(item.id, 'string', 'has the id')
    assert.include(item.types, AmfNamespace.aml.vocabularies.shapes.RecursiveShape, 'has the RecursiveShape type')
    assert.equal(item.name, 'relatedTo', 'has the name')
    // assert.typeOf(item.description, 'string', 'has the description');
    assert.deepEqual(item.inherits, [], 'has empty inherits')
    assert.deepEqual(item.or, [], 'has empty or')
    assert.deepEqual(item.and, [], 'has empty and')
    assert.deepEqual(item.xone, [], 'has empty xone')
    assert.deepEqual(item.values, [], 'has empty values')
    assert.deepEqual(item.customDomainProperties, [], 'has empty customDomainProperties')
    assert.typeOf(item.fixPoint, 'string', 'has the fixPoint')
  })

  test('processes a custom domain properties', ({ assert }) => {
    const shape = loader.lookupDeclaredShape(api, 'RecursiveShape')
    const result = serializer.unknownShape(shape) as IApiNodeShape
    const cdp = result.customDomainProperties as IApiCustomDomainProperty[]
    assert.typeOf(cdp, 'array', 'has the properties')
    assert.lengthOf(cdp, 1, 'has a single property')
    const [item] = cdp
    assert.typeOf(item.id, 'string', 'has the id')
    assert.include(item.extension.types, AmfNamespace.aml.vocabularies.data.Scalar, 'has the Scalar type')
    assert.equal(item.name, 'deprecated', 'has the extensionName')
    // @ts-expect-error Used in testing
    assert.typeOf(item.extension.value, 'string', 'has the value')
  })

  test('processes a SchemaShape', ({ assert }) => {
    const expects = loader.lookupExpects(api, '/xml', 'post')
    // @ts-expect-error Used in testing
    const payload = expects[loader._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.payload)][0]
    const shape = payload[serializer._getAmfKey(AmfNamespace.aml.vocabularies.shapes.schema)][0]
    const result = serializer.unknownShape(shape) as IApiSchemaShape
    assert.equal(result.mediaType, 'application/xml', 'has media type')
    assert.typeOf(result.raw, 'string', 'has raw')
    assert.typeOf(result.examples, 'array', 'has examples')

    // serializer recognizes that the this example is referenced to a payload and not to a type.
    assert.lengthOf(result.examples, 0, 'has no examples')
  })

  //
  // AMF resolvers links in the Editing pipeline. this probably can be removed.
  //
  test('resolves links', ({ assert }) => {
    // takes any operation that has a link-target in the shape
    const expects = loader.lookupExpects(api, '/files/{fileId}/comments', 'post')
    // @ts-expect-error Used in testing
    const payload = expects[loader._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.payload)][0]
    const shape = payload[serializer._getAmfKey(AmfNamespace.aml.vocabularies.shapes.schema)][0]
    const result = serializer.unknownShape(shape) as IApiNodeShape
    assert.equal(result.linkLabel, 'CommentWritable', 'has linkLabel')
    assert.typeOf(result.examples, 'array', 'has examples')

    // serializer recognizes that the this example is referenced to a payload and not to a type.
    assert.lengthOf(result.examples, 0, 'has single examples')
  }).skip()
})

test.group('OAS shapes', (group) => {
  let api: IAmfDocument
  let serializer: AmfSerializer
  group.setup(async () => {
    api = await loader.getGraph('petstore')
    serializer = new AmfSerializer()
    serializer.amf = api
  })

  test('processes the oneOf shape (xone)', ({ assert }) => {
    const expects = loader.lookupExpects(api, '/unions', 'patch')
    // @ts-expect-error Used in testing
    const payload = expects[serializer._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.payload)][0]
    const schema = payload[serializer._getAmfKey(AmfNamespace.aml.vocabularies.shapes.schema)][0]
    const result = serializer.unknownShape(schema)
    // Not sure why is this "any" shape.
    assert.include(result.types, AmfNamespace.aml.vocabularies.shapes.AnyShape, 'has the AnyShape type')
    assert.deepEqual(result.or, [], 'has empty or')
    assert.deepEqual(result.and, [], 'has empty and')
    assert.deepEqual(result.values, [], 'has empty values')
    assert.lengthOf(result.xone, 2, 'has the xone array')
    assert.typeOf(result.sourceMaps, 'object', 'has source maps')
  })

  test('processes the allOf shape (and)', ({ assert }) => {
    const expects = loader.lookupExpects(api, '/unions', 'post')
    // @ts-expect-error Used in testing
    const payload = expects[serializer._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.payload)][0]
    const schema = payload[serializer._getAmfKey(AmfNamespace.aml.vocabularies.shapes.schema)][0]
    const result = serializer.unknownShape(schema)
    assert.include(result.types, AmfNamespace.w3.shacl.NodeShape, 'has the NodeShape type')
    assert.deepEqual(result.or, [], 'has empty or')
    assert.deepEqual(result.xone, [], 'has empty xone')
    assert.deepEqual(result.values, [], 'has empty values')
    assert.lengthOf(result.and, 2, 'has the and array')
  })

  test('processes the anyOf shape (or)', ({ assert }) => {
    const returns = loader.lookupReturns(api, '/pet/listCommon', 'get')
    // @ts-expect-error Used in testing
    const payload = returns[0][serializer._getAmfKey(AmfNamespace.aml.vocabularies.apiContract.payload)][0]
    const schema = payload[serializer._getAmfKey(AmfNamespace.aml.vocabularies.shapes.schema)][0]
    const result = serializer.unknownShape(schema) as IApiArrayShape
    const type = result.items!
    // Not sure why is this "any" shape.
    assert.include(type.types, AmfNamespace.aml.vocabularies.shapes.AnyShape, 'has the AnyShape type')
    assert.deepEqual(type.and, [], 'has empty and')
    assert.deepEqual(type.xone, [], 'has empty xone')
    assert.deepEqual(type.values, [], 'has empty values')
    assert.lengthOf(type.or, 3, 'has the or array')
  })

  test('reads the discriminator value', ({ assert }) => {
    const shape = loader.lookupDeclaredShape(api, 'Pet')
    const result = serializer.unknownShape(shape) as IApiNodeShape
    assert.equal(result.discriminator, 'petType')
  })

  test('reads the xmlSerialization value', ({ assert }) => {
    const shape = loader.lookupDeclaredShape(api, 'Pet')
    const result = serializer.unknownShape(shape) as IApiNodeShape
    assert.typeOf(result.xmlSerialization, 'object', 'has the xmlSerialization')
    const xml = result.xmlSerialization
    assert.include(xml.types, AmfNamespace.aml.vocabularies.shapes.XMLSerializer, 'has the XMLSerializer type')
    assert.deepEqual(xml.customDomainProperties, [], 'has empty customDomainProperties')
    assert.isFalse(xml.attribute, 'has the attribute')
    assert.isFalse(xml.wrapped, 'has the wrapped')
    assert.equal(xml.name, 'pet', 'has the name')
  })

  test('reads the customDomainProperties value', ({ assert }) => {
    const shape = loader.lookupDeclaredShape(api, 'Pet')
    const result = serializer.unknownShape(shape) as IApiNodeShape
    assert.lengthOf(result.customDomainProperties, 1, 'has the customDomainProperties')
    const [cdp] = result.customDomainProperties
    assert.equal(cdp.name, 'swagger-router-model', 'has the name')
    const typed = cdp.extension as IApiScalarNode
    assert.include(typed.types, AmfNamespace.aml.vocabularies.data.Scalar, 'has the Scalar type')
    assert.deepEqual(typed.customDomainProperties, [], 'has empty customDomainProperties')
    assert.equal(typed.dataType, AmfNamespace.w3.xmlSchema.string, 'has the dataType')
    assert.equal(typed.value, 'io.swagger.petstore.model.Pet', 'has the value')
  })
})
