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 { IApiObjectNode, IApiScalarShape } from '../../../../src/amf/definitions/Shapes.js'
import { AmfNamespace } from '../../../../src/amf/definitions/Namespace.js'

const loader = new AmfLoader()

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

  test('translates an endpoint', ({ assert }) => {
    const shape = loader.lookupEndpoint(api, '/pet')
    const result = serializer.endPoint(shape)
    assert.typeOf(result, 'object', 'returns an object')
    assert.include(result.types, AmfNamespace.aml.vocabularies.apiContract.EndPoint, 'has the EndPoint type')
    assert.equal(result.path, '/pet', 'has the path')
    assert.typeOf(result.operations, 'array', 'has the operations')
    assert.lengthOf(result.operations, 2, 'has all operations')
    assert.typeOf(result.sourceMaps, 'object', 'has source maps')
  })

  test('adds the servers property', ({ assert }) => {
    const shape = loader.lookupEndpoint(api, '/pet')
    const result = serializer.endPoint(shape)
    assert.typeOf(result.servers, 'array', 'has the servers')
    assert.lengthOf(result.servers, 1, 'has a single server')
    assert.include(result.servers[0].types, AmfNamespace.aml.vocabularies.apiContract.Server, 'has the Server type')
  })

  test('adds the summary & description property', ({ assert }) => {
    const shape = loader.lookupEndpoint(api, '/user/{username}')
    const result = serializer.endPoint(shape)
    assert.equal(result.summary, 'Represents a user', 'has the summary')
    assert.typeOf(result.description, 'string', 'has description property')
  })
})

test.group('RAML endpoints', (group) => {
  let demoApi: IAmfDocument
  let arcDemoApi: IAmfDocument
  let demoSerializer: AmfSerializer
  let arcDemoSerializer: AmfSerializer
  group.setup(async () => {
    demoApi = await loader.getGraph('amf-helper-api')
    arcDemoApi = await loader.getGraph('arc-demo-api')
    demoSerializer = new AmfSerializer(demoApi)
    arcDemoSerializer = new AmfSerializer(arcDemoApi)
  })

  test('has annotations', ({ assert }) => {
    const shape = loader.lookupEndpoint(demoApi, '/annotations')
    const result = demoSerializer.endPoint(shape)
    assert.typeOf(result.customDomainProperties, 'array', 'has the array')
    assert.lengthOf(result.customDomainProperties, 1, 'has the annotation')
    const [item] = result.customDomainProperties
    assert.equal(item.name, 'clearanceLevel', 'has the name')
    const typed = item.extension as IApiObjectNode
    assert.typeOf(typed.properties, 'object', 'has the properties')
    assert.lengthOf(Object.keys(typed.properties), 2, 'has all properties')
  })

  test('has parameters', ({ assert }) => {
    const shape = loader.lookupEndpoint(demoApi, '/{groupId}/{assetId}/{version}')
    const result = demoSerializer.endPoint(shape)
    const { parameters } = result
    assert.typeOf(parameters, 'array', 'has the array')
    assert.lengthOf(parameters, 3, 'has the parameters')
    const [p1, p2, p3] = parameters
    assert.include(p1.types, AmfNamespace.aml.vocabularies.apiContract.Parameter, 'p1 has the type')
    assert.include(p2.types, AmfNamespace.aml.vocabularies.apiContract.Parameter, 'p2 has the type')
    assert.include(p3.types, AmfNamespace.aml.vocabularies.apiContract.Parameter, 'p3 has the type')
    assert.equal(p1.name, 'assetId', 'p1 has the name')
    assert.equal(p2.name, 'groupId', 'p2 has the name')
    assert.equal(p3.name, 'version', 'p3 has the name')
    assert.equal(p1.paramName, 'assetId', 'p1 has the paramName')
    assert.equal(p2.paramName, 'groupId', 'p2 has the paramName')
    assert.equal(p3.paramName, 'version', 'p3 has the paramName')
    assert.isTrue(p1.required, 'p1 has the required')
    assert.isTrue(p2.required, 'p2 has the required')
    assert.isTrue(p3.required, 'p3 has the required')
    assert.equal(p1.binding, 'path', 'p1 has the binding')
    assert.equal(p2.binding, 'path', 'p2 has the binding')
    assert.equal(p3.binding, 'path', 'p3 has the binding')
    assert.typeOf(p1.schema, 'object', 'p1 has the schema')
    assert.typeOf(p2.schema, 'object', 'p2 has the schema')
    assert.typeOf(p3.schema, 'object', 'p3 has the schema')
    assert.equal(
      (p1.schema as IApiScalarShape).dataType,
      AmfNamespace.w3.xmlSchema.string,
      'p1.schema has the dataType'
    )
    assert.equal(
      (p2.schema as IApiScalarShape).dataType,
      AmfNamespace.w3.xmlSchema.string,
      'p2.schema has the dataType'
    )
    assert.equal(
      (p3.schema as IApiScalarShape).dataType,
      AmfNamespace.aml.vocabularies.shapes.number,
      'p3.schema has the dataType'
    )
  })

  test('adds resource type definition', ({ assert }) => {
    const shape = loader.lookupEndpoint(demoApi, '/files/{fileId}')
    const result = demoSerializer.endPoint(shape)
    const { operations } = result
    const get = operations.find((o) => o.method === 'get')!
    const param = get.request!.queryParameters.find((p) => p.paramName === 'access_token')
    assert.ok(param)
  })

  test('adds a resource type extension', ({ assert }) => {
    const shape = loader.lookupEndpoint(arcDemoApi, '/products')
    const result = arcDemoSerializer.endPoint(shape)
    const { extends: extensions } = result
    assert.typeOf(extensions, 'array', 'has the extensions array')
    assert.lengthOf(extensions, 1, 'has a single extension')

    const [type] = extensions

    assert.typeOf(type.id, 'string', 'has the id')
    assert.typeOf(type.types, 'array', 'has the types')
    assert.include(
      type.types,
      AmfNamespace.aml.vocabularies.apiContract.ParametrizedResourceType,
      'has the type in the types'
    )
    assert.equal(type.name, 'RequestErrorResponse', 'has the name')
    assert.typeOf(type.target, 'object', 'has the target definition')
  })

  test('adds a trait extension', ({ assert }) => {
    const shape = loader.lookupEndpoint(arcDemoApi, '/orgs/{orgId}')
    const result = arcDemoSerializer.endPoint(shape)
    const { extends: extensions } = result
    assert.typeOf(extensions, 'array', 'has the extensions array')
    assert.lengthOf(extensions, 1, 'has a single extension')

    const [type] = extensions

    assert.typeOf(type.id, 'string', 'has the id')
    assert.typeOf(type.types, 'array', 'has the types')
    assert.include(type.types, AmfNamespace.aml.vocabularies.apiContract.ParametrizedTrait, 'has the type in the types')
    assert.equal(type.name, 'RateLimited', 'has the name')
    assert.typeOf(type.target, 'object', 'has the target definition')
  })

  test('adds both a trait and a resource type extensions', ({ assert }) => {
    const shape = loader.lookupEndpoint(arcDemoApi, '/people')
    const result = arcDemoSerializer.endPoint(shape)
    const { extends: extensions } = result
    assert.typeOf(extensions, 'array', 'has the extensions array')
    assert.lengthOf(extensions, 2, 'has both extensions')

    const type = extensions.find((e) =>
      e.types.includes(AmfNamespace.aml.vocabularies.apiContract.ParametrizedResourceType)
    )
    const trait = extensions.find((e) => e.types.includes(AmfNamespace.aml.vocabularies.apiContract.ParametrizedTrait))

    assert.ok(type, 'has the resource type')
    assert.ok(trait, 'has the trait')
  })
})
