// tslint:disable:object-literal-sort-keys
import * as validator from 'class-validator'
import * as _ from 'lodash'

import { validationMetadatasToSchemas } from '../src'

class Post {}
class Comment {}

enum PostType {
  Public,
  Private
}
enum Role {
  Anonymous = 'anonymous',
  User = 'user'
}

// @ts-ignore: not referenced
class User {
  // Common validation decorators:
  @validator.IsDefined() isDefined: string
  @validator.Equals('x') equalsString: string
  @validator.Equals(123.23)
  equalsNumber: number
  @validator.Equals({ someKey: 'x' })
  equalsComplex: object
  @validator.NotEquals('x') notEqualsString: string
  @validator.NotEquals(123.23)
  notEqualsNumber: number
  @validator.NotEquals({ someKey: 'x' })
  notEqualsComplex: object
  @validator.IsEmpty() isEmpty?: string
  @validator.IsNotEmpty() isNotEmpty: string
  @validator.IsIn([])
  isInEmpty: string
  @validator.IsIn(['x', 'y'])
  isInString: string
  @validator.IsIn([1, 2])
  isInNumber: number
  @validator.IsIn([Post, Comment])
  isInClass: Post | Comment
  @validator.IsNotIn(['x', 'y'])
  isNotIn: string

  // Type validation decorators:
  @validator.IsBoolean() isBoolean: boolean
  @validator.IsDate() isDate: Date
  @validator.IsString() isString: string
  @validator.IsNumber() isNumber: number
  @validator.IsInt() isInt: number
  @validator.IsArray() isArray: any[]
  @validator.IsEnum(PostType) isEnum: PostType
  @validator.IsEnum(Role) isEnumWithValue: Role

  // Number validation decorators:
  @validator.IsDivisibleBy(4)
  isDivisibleByInt: number
  @validator.IsDivisibleBy(1.1)
  isDivisibleByFloat: number
  @validator.IsPositive() isPositive: number
  @validator.IsNegative() isNegative: number
  @validator.Max(10)
  max: number
  @validator.Min(1)
  min: number

  // Date validation decorators:
  @validator.MinDate(new Date('2017'))
  minDate: Date
  @validator.MaxDate(new Date('2017'))
  maxDate: Date

  // String-type validation decorators:
  @validator.IsBooleanString() isBooleanString: string
  @validator.IsDateString() isDateString: string
  @validator.IsNumberString() isNumberString: string

  // String validation decorators:
  @validator.Contains('seed') contains: string
  @validator.NotContains('seed') notContains: string
  @validator.IsAlpha() isAlpha: string
  @validator.IsAlphanumeric() isAlphanumeric: string
  @validator.IsAscii() isAscii: string
  @validator.IsBase64() isBase64: string
  @validator.IsByteLength(1, 10)
  isByteLength: string
  @validator.IsCreditCard() isCreditCard: string
  @validator.IsCurrency() isCurrency: string
  @validator.IsEmail() isEmail: string
  @validator.IsFQDN() isFQDN: string
  @validator.IsFullWidth() isFullWidth: string
  @validator.IsHalfWidth() isHalfWidth: string
  @validator.IsVariableWidth() isVariableWidth: string
  @validator.IsHexColor() isHexColor: string
  @validator.IsHexadecimal() isHexadecimal: string
  @validator.IsIP() isIPv4: string
  @validator.IsIP('6') isIPv6: string
  @validator.IsISBN() isISBN: string
  @validator.IsISIN() isISIN: string
  @validator.IsISO8601() isISO8601: string
  @validator.IsJSON() isJSON: string
  @validator.IsLowercase() isLowerCase: string
  @validator.IsMobilePhone('en-GB') isMobilePhone: string
  @validator.IsMongoId() isMongoId: string
  @validator.IsMultibyte() isMultibyte: string
  @validator.IsSurrogatePair() isSurrogatePair: string
  @validator.IsUrl() isUrl: string
  @validator.IsUUID() isUUID: string
  @validator.IsUppercase() isUpperCase: string
  @validator.Length(1)
  length: string
  @validator.Length(1, 10)
  lengthWithMax: string
  @validator.MinLength(1)
  minLength: string
  @validator.MaxLength(10)
  maxLength: string
  @validator.Matches(/\d[a-zA-Z]+/)
  matches: string
  @validator.IsMilitaryTime() isMilitaryTime: string

  // Array validation decorators:
  @validator.ArrayContains(['x', 'y'])
  arrayContainsString: string[]
  @validator.ArrayContains([true, 1])
  arrayContainsVarious: any[]
  @validator.ArrayContains(['x', { someKey: 'y' }])
  arrayContainsComplex: any[]
  @validator.ArrayNotContains(['x', 'y'])
  arrayNotContains: string[]
  @validator.ArrayNotContains([{ someKey: 'x' }, 3])
  arrayNotContainsComplex: any[]
  @validator.ArrayNotEmpty() arrayNotEmpty: any[]
  @validator.ArrayMinSize(1)
  arrayMinSize: any[]
  @validator.ArrayMaxSize(10)
  arrayMaxSize: any[]
  @validator.ArrayUnique() arrayUnique: any[]
}

const metadata = _.get(
  validator.getFromContainer(validator.MetadataStorage),
  'validationMetadatas'
)
const schemas = validationMetadatasToSchemas(metadata)

describe('defaultConverters', () => {
  it('generates OpenAPI schemas from class-validator metadata', () => {
    expect(schemas).toEqual({
      User: {
        properties: {
          isDefined: {},
          equalsString: { type: 'string', enum: ['x'] },
          equalsNumber: { type: 'number', enum: [123.23] },
          equalsComplex: {},
          notEqualsString: { not: { type: 'string', enum: ['x'] } },
          notEqualsNumber: { not: { type: 'number', enum: [123.23] } },
          notEqualsComplex: {},
          isEmpty: {
            anyOf: [
              { type: 'string', enum: [''] },
              {
                nullable: true,
                not: {
                  anyOf: [
                    { type: 'string' },
                    { type: 'number' },
                    { type: 'boolean' },
                    { type: 'integer' },
                    { type: 'array' },
                    { type: 'object' }
                  ]
                }
              }
            ]
          },
          isNotEmpty: { minLength: 1, type: 'string' },
          isInEmpty: {},
          isInString: { enum: ['x', 'y'], type: 'string' },
          isInNumber: { enum: [1, 2], type: 'number' },
          isInClass: {},
          isNotIn: { not: { enum: ['x', 'y'], type: 'string' } },

          isBoolean: { type: 'boolean' },
          isDate: {
            oneOf: [
              { format: 'date', type: 'string' },
              { format: 'date-time', type: 'string' }
            ]
          },
          isString: { type: 'string' },
          isNumber: { type: 'number' },
          isInt: { type: 'integer' },
          isArray: { items: {}, type: 'array' },
          isEnum: { type: 'string', enum: ['Public', 'Private', 0, 1] },
          isEnumWithValue: { type: 'string', enum: ['anonymous', 'user'] },

          isDivisibleByInt: { multipleOf: 4, type: 'number' },
          isDivisibleByFloat: { multipleOf: 1.1, type: 'number' },
          isPositive: { type: 'number', minimum: 0, exclusiveMinimum: true },
          isNegative: { type: 'number', maximum: 0, exclusiveMaximum: true },
          max: { type: 'number', maximum: 10 },
          min: { type: 'number', minimum: 1 },

          minDate: {
            description: `After ${new Date('2017').toJSON()}`,
            oneOf: [
              { format: 'date', type: 'string' },
              { format: 'date-time', type: 'string' }
            ]
          },
          maxDate: {
            description: `Before ${new Date('2017').toJSON()}`,
            oneOf: [
              { format: 'date', type: 'string' },
              { format: 'date-time', type: 'string' }
            ]
          },

          isBooleanString: { type: 'string', enum: ['true', 'false'] },
          isDateString: {
            pattern: 'd{4}-[01]d-[0-3]dT[0-2]d:[0-5]d:[0-5]d.d+Z?',
            type: 'string'
          },
          isNumberString: { pattern: '^[-+]?[0-9]+$', type: 'string' },

          contains: { pattern: 'seed', type: 'string' },
          notContains: { not: { pattern: 'seed' }, type: 'string' },
          isAlpha: { pattern: '^[a-zA-Z]+$', type: 'string' },
          isAlphanumeric: { pattern: '^[0-9a-zA-Z]+$', type: 'string' },
          isAscii: { pattern: '^[\\x00-\\x7F]+$', type: 'string' },
          isBase64: { format: 'base64', type: 'string' },
          isByteLength: { type: 'string' },
          isCreditCard: { format: 'credit-card', type: 'string' },
          isCurrency: { format: 'currency', type: 'string' },
          isEmail: { format: 'email', type: 'string' },
          isFQDN: { format: 'hostname', type: 'string' },
          isFullWidth: {
            pattern:
              '[^\\u0020-\\u007E\\uFF61-\\uFF9F\\uFFA0-\\uFFDC\\uFFE8-\\uFFEE0-9a-zA-Z]',
            type: 'string'
          },
          isHalfWidth: {
            pattern:
              '[\\u0020-\\u007E\\uFF61-\\uFF9F\\uFFA0-\\uFFDC\\uFFE8-\\uFFEE0-9a-zA-Z]',
            type: 'string'
          },
          isVariableWidth: { type: 'string' },
          isHexColor: {
            pattern: '^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$',
            type: 'string'
          },
          isHexadecimal: { pattern: '^[0-9a-fA-F]+$', type: 'string' },
          isIPv4: { format: 'ipv4', type: 'string' },
          isIPv6: { format: 'ipv6', type: 'string' },
          isISBN: { format: 'isbn', type: 'string' },
          isISIN: { format: 'isin', type: 'string' },
          isISO8601: {
            oneOf: [
              { format: 'date', type: 'string' },
              { format: 'date-time', type: 'string' }
            ]
          },
          isJSON: { format: 'json', type: 'string' },
          isLowerCase: { type: 'string' },
          isMobilePhone: { format: 'mobile-phone', type: 'string' },
          isMongoId: {
            pattern: '^[0-9a-fA-F]{24}$',
            type: 'string'
          },
          isMultibyte: {
            pattern: '[^\\x00-\\x7F]',
            type: 'string'
          },
          isSurrogatePair: {
            pattern: '[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]',
            type: 'string'
          },
          isUrl: { format: 'url', type: 'string' },
          isUUID: { format: 'uuid', type: 'string' },
          length: { minLength: 1, type: 'string' },
          lengthWithMax: { minLength: 1, maxLength: 10, type: 'string' },
          isUpperCase: { type: 'string' },
          minLength: { minLength: 1, type: 'string' },
          maxLength: { maxLength: 10, type: 'string' },
          matches: { pattern: '\\d[a-zA-Z]+', type: 'string' },
          isMilitaryTime: {
            pattern: '^([01]\\d|2[0-3]):?([0-5]\\d)$',
            type: 'string'
          },

          arrayContainsString: {
            type: 'array',
            not: {
              anyOf: [
                { items: { not: { type: 'string', enum: ['x'] } } },
                { items: { not: { type: 'string', enum: ['y'] } } }
              ]
            }
          },
          arrayContainsVarious: {
            type: 'array',
            not: {
              anyOf: [
                { items: { not: { type: 'boolean', enum: [true] } } },
                { items: { not: { type: 'number', enum: [1] } } }
              ]
            }
          },
          arrayContainsComplex: { type: 'array', items: {} },
          arrayNotContains: {
            type: 'array',
            items: {
              not: {
                anyOf: [
                  { enum: ['x'], type: 'string' },
                  { enum: ['y'], type: 'string' }
                ]
              }
            }
          },
          arrayNotContainsComplex: { type: 'array', items: {} },
          arrayNotEmpty: { type: 'array', items: {}, minItems: 1 },
          arrayMinSize: { type: 'array', items: {}, minItems: 1 },
          arrayMaxSize: { type: 'array', items: {}, maxItems: 10 },
          arrayUnique: { type: 'array', items: {}, uniqueItems: true }
        },
        required: expect.any(Array),
        type: 'object'
      }
    })
  })
})
