
// tslint:disable-next-line: no-implicit-dependencies no-var-requires
const Ajv = require('ajv');

import { ValidationContext }        from '../types';
import { validate,
         getType }                  from '../validator';
import { pick,
         patch }                    from '../picker';
import { compile }                  from '../compiler';
import { generateJsonSchemaObject } from '../codegen';
import { serialize, deserialize }   from '../serializer';
import * as op                      from '../operators';



describe("json-schema-1", function() {
    it("json-schema-1-1", function() {
        const z = compile(`
            type Foo = string;
            interface A {
                a: Foo;
                @range(3, 5)
                b: number;
                @maxLength(3)
                c: Foo;
                [propNames: /^[e-f]$/]: number;
                i?: string;
            }
        `);
        const schema = generateJsonSchemaObject(z);

        const ajv = new Ajv({allErrors: true});
        // tslint:disable-next-line: no-implicit-dependencies
        ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
        const ajvValidate = ajv.addSchema(schema).getSchema('#/definitions/A');

        expect(ajvValidate({a: 'z', b: 5, c: '123'})).toEqual(true);

        expect(ajvValidate({b: 5, c: '123'})).toEqual(false);
        expect(ajvValidate.errors).toEqual([{
            keyword: 'required',
            dataPath: '',
            schemaPath: '#/required',
            params: { missingProperty: 'a' },
            message: 'should have required property \'a\'',
        }]);

        expect(ajvValidate({a: 1, b: 5, c: '123'})).toEqual(false);
        expect(ajvValidate.errors).toEqual([{
            keyword: 'type',
            dataPath: '.a',
            schemaPath: '#/definitions/Foo/type',
            params: { type: 'string' },
            message: 'should be string',
        }]);

        expect(ajvValidate({a: 'z', b: 6, c: '123'})).toEqual(false);
        expect(ajvValidate.errors).toEqual([{
            keyword: 'maximum',
            dataPath: '.b',
            schemaPath: '#/properties/b/maximum',
            params: { comparison: '<=', limit: 5, exclusive: false },
            message: 'should be <= 5',
        }]);

        expect(ajvValidate({a: 'z', b: 5, c: '1234'})).toEqual(false);
        expect(ajvValidate.errors).toEqual([{
            keyword: 'maxLength',
            dataPath: '.c',
            schemaPath: '#/properties/c/maxLength',
            params: { limit: 3 },
            message: 'should NOT be longer than 3 characters',
        }]);

        expect(ajvValidate({b: 6, c: '1234'})).toEqual(false);
        expect(ajvValidate.errors).toEqual([{
            keyword: 'required',
            dataPath: '',
            schemaPath: '#/required',
            params: { missingProperty: 'a' },
            message: 'should have required property \'a\'',
        }, {
            keyword: 'maximum',
            dataPath: '.b',
            schemaPath: '#/properties/b/maximum',
            params: { comparison: '<=', limit: 5, exclusive: false },
            message: 'should be <= 5',
        }, {
            keyword: 'maxLength',
            dataPath: '.c',
            schemaPath: '#/properties/c/maxLength',
            params: { limit: 3 },
            message: 'should NOT be longer than 3 characters',
        }]);

        expect(ajvValidate({a: 'z', b: 5, c: '123', d: 11})).toEqual(false);
        expect(ajvValidate.errors).toEqual([{
            keyword: 'additionalProperties',
            dataPath: '',
            schemaPath: '#/additionalProperties',
            params: { additionalProperty: 'd' },
            message: 'should NOT have additional properties',
        }]);

        expect(ajvValidate({a: 'z', b: 5, c: '123', d: '11'})).toEqual(false);
        expect(ajvValidate.errors).toEqual([{
            keyword: 'additionalProperties',
            dataPath: '',
            schemaPath: '#/additionalProperties',
            params: { additionalProperty: 'd' },
            message: 'should NOT have additional properties',
        }]);

        expect(ajvValidate({a: 'z', b: 5, c: '123', e: 11})).toEqual(true);

        expect(ajvValidate({a: 'z', b: 5, c: '123', e: '11'})).toEqual(false);
        expect(ajvValidate.errors).toEqual([{
            keyword: 'type',
            dataPath: '[\'e\']',
            schemaPath: '#/patternProperties/%5E%5Be-f%5D%24/type',
            params: { type: 'number' },
            message: 'should be number',
        }]);

        expect(ajvValidate({a: 'z', b: 5, c: '123', i: '11'})).toEqual(true);

        expect(ajvValidate({a: 'z', b: 5, c: '123', i: 11})).toEqual(false);
        expect(ajvValidate.errors).toEqual([{
            keyword: 'type',
            dataPath: '.i',
            schemaPath: '#/properties/i/type',
            params: { type: 'string' },
            message: 'should be string',
        }]);
    });
    it("json-schema-1-2", function() {
        const z = compile(`
            type Foo = string;
            interface A {
                a: Foo;
                @range(3, 5)
                b: number;
                @maxLength(3)
                c: Foo;
                [propNames: /^[e-f]$/]: number;
                i?: string;
            }
            interface B {
                x: A;
            }
        `);
        const schema = generateJsonSchemaObject(z);

        const ajv = new Ajv({allErrors: true});
        // tslint:disable-next-line: no-implicit-dependencies
        ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
        const ajvValidate = ajv.addSchema(schema).getSchema('#/definitions/B');

        expect(ajvValidate({x: {a: 'z', b: 5, c: '123'}})).toEqual(true);

        expect(ajvValidate({x: {b: 5, c: '123'}})).toEqual(false);
        expect(ajvValidate.errors).toEqual([{
            keyword: 'required',
            dataPath: '.x',
            schemaPath: '#/required',
            params: { missingProperty: 'a' },
            message: 'should have required property \'a\'',
        }]);

        expect(ajvValidate({x: {a: 1, b: 5, c: '123'}})).toEqual(false);
        expect(ajvValidate.errors).toEqual([{
            keyword: 'type',
            dataPath: '.x.a',
            schemaPath: '#/definitions/Foo/type',
            params: { type: 'string' },
            message: 'should be string',
        }]);
    });
    it("json-schema-1-3", function() {
        const z = compile(`
            type Foo = string;
            type A = {
                a: Foo,
                @range(3, 5)
                b: number,
                @maxLength(3)
                c: Foo,
                [propNames: /^[e-f]$/]: number,
                i?: string,
            };
            type B = {
                x: A,
            };
        `);
        const schema = generateJsonSchemaObject(z);

        const ajv = new Ajv({allErrors: true});
        // tslint:disable-next-line: no-implicit-dependencies
        ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
        const ajvValidate = ajv.addSchema(schema).getSchema('#/definitions/B');

        expect(ajvValidate({x: {a: 'z', b: 5, c: '123'}})).toEqual(true);

        expect(ajvValidate({x: {b: 5, c: '123'}})).toEqual(false);
        expect(ajvValidate.errors).toEqual([{
            keyword: 'required',
            dataPath: '.x',
            schemaPath: '#/required',
            params: { missingProperty: 'a' },
            message: 'should have required property \'a\'',
        }]);

        expect(ajvValidate({x: {a: 1, b: 5, c: '123'}})).toEqual(false);
        expect(ajvValidate.errors).toEqual([{
            keyword: 'type',
            dataPath: '.x.a',
            schemaPath: '#/definitions/Foo/type',
            params: { type: 'string' },
            message: 'should be string',
        }]);
    });
    it("json-schema-1-4", function() {
        const z = compile(`
            type Foo = string|number;
        `);
        const schema = generateJsonSchemaObject(z);

        const ajv = new Ajv({allErrors: true});
        // tslint:disable-next-line: no-implicit-dependencies
        ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
        const ajvValidate = ajv.addSchema(schema).getSchema('#/definitions/Foo');

        expect(ajvValidate(11)).toEqual(true);
        expect(ajvValidate('11')).toEqual(true);

        expect(ajvValidate(true)).toEqual(false);
        expect(ajvValidate.errors).toEqual([{
            keyword: 'type',
            dataPath: '',
            schemaPath: '#/anyOf/0/type',
            params: { type: 'string' },
            message: 'should be string',
        }, {
            keyword: 'type',
            dataPath: '',
            schemaPath: '#/anyOf/1/type',
            params: { type: 'number' },
            message: 'should be number',
        }, {
            keyword: 'anyOf',
            dataPath: '',
            schemaPath: '#/anyOf',
            params: {},
            message: 'should match some schema in anyOf',
        }]);
    });
    it("json-schema-1-5", function() {
        const z = compile(`
            type Foo = number[];
        `);
        const schema = generateJsonSchemaObject(z);

        const ajv = new Ajv({allErrors: true});
        // tslint:disable-next-line: no-implicit-dependencies
        ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
        const ajvValidate = ajv.addSchema(schema).getSchema('#/definitions/Foo');

        expect(ajvValidate([11, 13])).toEqual(true);

        expect(ajvValidate([11, 13, '17'])).toEqual(false);
        expect(ajvValidate.errors).toEqual([{
            keyword: 'type',
            dataPath: '[2]',
            schemaPath: '#/items/type',
            params: { type: 'number' },
            message: 'should be number',
        }]);
    });
    it("json-schema-1-6", function() {
        const z = compile(`
            type Foo = (number|boolean)[];
        `);
        const schema = generateJsonSchemaObject(z);

        const ajv = new Ajv({allErrors: true});
        // tslint:disable-next-line: no-implicit-dependencies
        ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
        const ajvValidate = ajv.addSchema(schema).getSchema('#/definitions/Foo');

        expect(ajvValidate([11, 13, false])).toEqual(true);

        expect(ajvValidate([11, 13, '17'])).toEqual(false);
        expect(ajvValidate.errors).toEqual([{
            keyword: 'type',
            dataPath: '[2]',
            schemaPath: '#/items/anyOf/0/type',
            params: { type: 'number' },
            message: 'should be number',
        }, {
            keyword: 'type',
            dataPath: '[2]',
            schemaPath: '#/items/anyOf/1/type',
            params: { type: 'boolean' },
            message: 'should be boolean',
        }, {
            keyword: 'anyOf',
            dataPath: '[2]',
            schemaPath: '#/items/anyOf',
            params: {},
            message: 'should match some schema in anyOf',
        }]);
    });
    it("json-schema-1-6", function() {
        const z = compile(`
            type Foo = [number, boolean];
        `);
        const schema = generateJsonSchemaObject(z);

        const ajv = new Ajv({allErrors: true});
        // tslint:disable-next-line: no-implicit-dependencies
        ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
        const ajvValidate = ajv.addSchema(schema).getSchema('#/definitions/Foo');

        expect(ajvValidate([11, false])).toEqual(true);     // NOTE: In JSON Schema, compile to `(number | boolean)[]`
        expect(ajvValidate([false, 11, 13])).toEqual(true);

        expect(ajvValidate([11, 13, '17'])).toEqual(false);
        expect(ajvValidate.errors).toEqual([{
            keyword: 'type',
            dataPath: '[2]',
            schemaPath: '#/items/anyOf/0/type',
            params: { type: 'number' },
            message: 'should be number',
        }, {
            keyword: 'type',
            dataPath: '[2]',
            schemaPath: '#/items/anyOf/1/type',
            params: { type: 'boolean' },
            message: 'should be boolean',
        }, {
            keyword: 'anyOf',
            dataPath: '[2]',
            schemaPath: '#/items/anyOf',
            params: {},
            message: 'should match some schema in anyOf',
        }]);
    });
    it("json-schema-1-7", function() {
        const z = compile(`
            enum Foo {
                A = 'a',
                B = 10,
                C = 'b',
            }
        `);
        const schema = generateJsonSchemaObject(z);

        const ajv = new Ajv({allErrors: true});
        // tslint:disable-next-line: no-implicit-dependencies
        ajv.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
        const ajvValidate = ajv.addSchema(schema).getSchema('#/definitions/Foo');

        expect(ajvValidate('a')).toEqual(true);
        expect(ajvValidate(10)).toEqual(true);
        expect(ajvValidate('b')).toEqual(true);

        expect(ajvValidate(11)).toEqual(false);
        expect(ajvValidate.errors).toEqual([{
            keyword: 'enum',
            dataPath: '',
            schemaPath: '#/enum',
            params: { allowedValues: ['a', 10, 'b'] },
            message: 'should be equal to one of the allowed values',
        }]);

        expect(ajvValidate('c')).toEqual(false);
        expect(ajvValidate.errors).toEqual([{
            keyword: 'enum',
            dataPath: '',
            schemaPath: '#/enum',
            params: { allowedValues: ['a', 10, 'b'] },
            message: 'should be equal to one of the allowed values',
        }]);
    });
});
