import {ApiEdgeError} from "../src/query/ApiEdgeError";
const tap = require('tap');

import {Api} from "../src/Api";
import * as builder from "../src/query/ApiQueryBuilder";
import {OneToOneRelation} from "../src/relations/OneToOneRelation";
import {OneToManyRelation} from "../src/relations/OneToManyRelation";

import {StudentEdge} from "./env/edges/StudentEdge";
import {ClassEdge} from "./env/edges/ClassEdge";
import {CourseEdge} from "./env/edges/CourseEdge";
import {CourseTypeEdge} from "./env/edges/CourseTypeEdge";
import {SchoolEdge} from "./env/edges/SchoolEdge";
import {ApiRequestType, ApiRequest} from "../src/request/ApiRequest";

const studentEdge = new StudentEdge,
    classEdge = new ClassEdge,
    courseEdge = new CourseEdge,
    courseTypeEdge = new CourseTypeEdge,
    schoolEdge = new SchoolEdge;

var api: Api;

tap.test('creating the API should work', (t: any) => {
    api = new Api({name: 'test-service', version: '1.0'})
        .edge(studentEdge)
        .edge(classEdge)
        .edge(courseEdge)
        .edge(courseTypeEdge)
        .edge(schoolEdge)
        .relation(new OneToOneRelation(courseEdge, courseTypeEdge))
        .relation(new OneToManyRelation(courseTypeEdge, courseEdge))
        .relation(new OneToManyRelation(studentEdge, courseEdge))
        .relation(new OneToOneRelation(studentEdge, classEdge))
        .relation(new OneToOneRelation(classEdge, schoolEdge))
        .relation(new OneToOneRelation(courseEdge, classEdge))
        .relation(new OneToManyRelation(classEdge, studentEdge))
        .relation(new OneToManyRelation(classEdge, courseEdge))
        .relation(new OneToManyRelation(schoolEdge, studentEdge))
        .relation(new OneToManyRelation(schoolEdge, classEdge));

    t.end()
});

tap.test('Any type queries should fail', (t: any) => {
    try {
        let request = new ApiRequest(api);
        request.type = ApiRequestType.Any;
        api.buildQuery(request);
        t.ok(false, 'an invalid query should not succeed');
    }
    catch(e) {
        t.ok(e instanceof ApiEdgeError);
        t.equal(e.message, 'Unsupported Query Type');
        t.equal(e.status, 400);
    }
    t.end()
});

tap.test('Change type queries should fail', (t: any) => {
    try {
        let request = new ApiRequest(api);
        request.type = ApiRequestType.Change;
        api.buildQuery(request);
        t.ok(false, 'an invalid query should not succeed');
    }
    catch(e) {
        t.ok(e instanceof ApiEdgeError);
        t.equal(e.message, 'Unsupported Query Type');
        t.equal(e.status, 400);
    }
    t.end()
});

tap.test('/schools', async (t: any) => {
    const request = await api.parseRequest([ 'schools' ]),
        query = api.buildQuery(request);

    t.equal(query.steps.length, 2, 'should build a 2 step query');
    t.ok(query.steps[0] instanceof builder.ExtendContextQueryStep);
    t.ok(query.steps[1] instanceof builder.QueryEdgeQueryStep);

    return query.execute()
        .then(resp => {
            t.same(resp.data,
                [
                    {
                        address: "16, Test street, North Pole, HA23535",
                        id: "s1",
                        name: "First School",
                        phone: "435234523"
                    },
                    {
                        address: "12, Test street, North Pole, HA23535",
                        id: "s2",
                        name: "Second School",
                        phone: "456345283"
                    }
                ]);
            t.equal(resp.metadata, null);
            t.end()
        })
        .catch((e) => {
            t.ok(false, "a valid query should not fail");
            t.end()
        });
});

tap.test('/schools/s2', async (t: any) => {
    const request = await api.parseRequest([ 'schools', 's2' ]),
        query = api.buildQuery(request);

    t.equal(query.steps.length, 3, 'should build a 3 step query');
    t.ok(query.steps[0] instanceof builder.ExtendContextLiveQueryStep);
    t.ok(query.steps[1] instanceof builder.ExtendContextQueryStep);
    t.ok(query.steps[2] instanceof builder.QueryEdgeQueryStep);

    return query.execute()
        .then(resp => {
            t.same(resp.data,
                {
                    address: "12, Test street, North Pole, HA23535",
                    id: "s2",
                    name: "Second School",
                    phone: "456345283"
                });
            t.equal(resp.metadata, null);
            t.end()
        })
        .catch(() => {
            t.ok(false, "a valid query should not fail");
            t.end()
        });
});

tap.test('/schools/s5', async (t: any) => {
    const request = await api.parseRequest([ 'schools', 's5' ]),
        query = api.buildQuery(request);

    return query.execute()
        .then(() => {
            t.ok(false, "an invalid query should not succeed");
            t.end()
        })
        .catch(e => {
            t.ok(e instanceof ApiEdgeError);
            t.equal(e.status, 404);
            t.equal(e.message, 'Not Found');
            t.end()
        });
});

tap.test('/schools/s1/classes', async (t: any) => {
    const request = await api.parseRequest([ 'schools', 's1', 'classes' ]),
        query = api.buildQuery(request);

    t.equal(query.steps.length, 5, 'should build a 5 step query');
    t.ok(query.steps[0] instanceof builder.ExtendContextLiveQueryStep, 'EXTEND');
    t.ok(query.steps[1] instanceof builder.QueryEdgeQueryStep, 'QUERY /schools');
    t.ok(query.steps[2] instanceof builder.RelateQueryStep, 'RELATE schoolId');
    t.ok(query.steps[3] instanceof builder.ExtendContextQueryStep, 'APPLY PARAMS');
    t.ok(query.steps[4] instanceof builder.QueryEdgeQueryStep, 'QUERY /classes');

    return query.execute()
        .then(resp => {
            t.same(resp.data,
                [
                    {
                        id: "c1",
                        name: "A",
                        year: 1,
                        room: "Room 1",
                        schoolId: "s1"
                    }
                ]);
            t.equal(resp.metadata, null);
            t.end()
        })
        .catch(() => {
            t.ok(false, "a valid query should not fail");
            t.end()
        });
});

tap.test('/schools/s1/classes/c1', async (t: any) => {
    const request = await api.parseRequest([ 'schools', 's1', 'classes', 'c1' ]),
        query = api.buildQuery(request);

    t.equal(query.steps.length, 6, 'should build a 6 step query');
    t.ok(query.steps[0] instanceof builder.ExtendContextLiveQueryStep, 'EXTEND');
    t.ok(query.steps[1] instanceof builder.QueryEdgeQueryStep, 'QUERY /schools');
    t.ok(query.steps[2] instanceof builder.RelateQueryStep, 'RELATE schoolId');
    t.ok(query.steps[3] instanceof builder.ExtendContextLiveQueryStep, 'EXTEND');
    t.ok(query.steps[4] instanceof builder.ExtendContextQueryStep, 'APPLY PARAMS');
    t.ok(query.steps[5] instanceof builder.QueryEdgeQueryStep, 'QUERY /classes');

    return query.execute()
        .then(resp => {
            t.same(resp.data,
                {
                    id: "c1",
                    name: "A",
                    year: 1,
                    room: "Room 1",
                    schoolId: "s1"
                });
            t.equal(resp.metadata, null);
            t.end()
        })
        .catch(() => {
            t.ok(false, "a valid query should not fail");
            t.end()
        });
});

tap.test('/students/s2/class', async (t: any) => {
    const request = await api.parseRequest([ 'students', 's2', 'class' ]),
        query = api.buildQuery(request);

    t.equal(query.steps.length, 5, 'should build a 5 step query');
    t.ok(query.steps[0] instanceof builder.ExtendContextLiveQueryStep, 'EXTEND');
    t.ok(query.steps[1] instanceof builder.QueryEdgeQueryStep, 'QUERY /students');
    t.ok(query.steps[2] instanceof builder.ProvideIdQueryStep, 'PROVIDE ID: classId');
    t.ok(query.steps[3] instanceof builder.ExtendContextQueryStep, 'APPLY PARAMS');
    t.ok(query.steps[4] instanceof builder.QueryEdgeQueryStep, 'QUERY /classes');

    return query.execute()
        .then(resp => {
            t.same(resp.data, {
                id: "c1",
                name: "A",
                year: 1,
                room: "Room 1",
                schoolId: "s1"
            });
            t.equal(resp.metadata, null);
            t.end()
        })
        .catch(() => {
            t.ok(false, "a valid query should not fail");
            t.end()
        });
});

tap.test('/schools/s1/classes/c2', async (t: any) => {
    const request = await api.parseRequest([ 'schools', 's1', 'classes', 'c2' ]),
        query = api.buildQuery(request);

    return query.execute()
        .then(() => {
            t.ok(false, "an invalid query should not succeed");
            t.end()
        })
        .catch(e => {
            t.ok(e instanceof ApiEdgeError);
            t.equal(e.status, 404);
            t.equal(e.message, 'Not Found');
            t.end()
        });
});

tap.test('POST /schools', async (t: any) => {
    const request = await api.parseRequest([ 'schools' ]);
    request.type = ApiRequestType.Create;
    const body = request.body = {
        address: "12, Test street, North Pole, HA23535",
        id: "s3",
        name: "Third School",
        phone: "456345283"
    };
    const query = api.buildQuery(request);

    t.equal(query.steps.length, 2, 'should build a 2 step query');
    t.ok(query.steps[0] instanceof builder.SetBodyQueryStep);
    t.ok(query.steps[1] instanceof builder.QueryEdgeQueryStep);

    return query.execute()
        .then(resp => {
            t.same(resp.data, body);
            t.equal(resp.metadata, null);
            t.end()
        })
        .catch(() => {
            t.ok(false, "a valid query should not fail");
            t.end()
        });
});

tap.test('DELETE /schools/s3', async (t: any) => {
    const request = await api.parseRequest([ 'schools', 's3' ]);
    request.type = ApiRequestType.Delete;
    const query = api.buildQuery(request);

    t.equal(query.steps.length, 3, 'should build a 3 step query');
    t.ok(query.steps[0] instanceof builder.ExtendContextLiveQueryStep);
    t.ok(query.steps[1] instanceof builder.ExtendContextQueryStep);
    t.ok(query.steps[2] instanceof builder.QueryEdgeQueryStep);

    return query.execute()
        .then(resp => {
            t.equal(resp.metadata, null);
            t.end()
        })
        .catch(() => {
            t.ok(false, "a valid query should not fail");
            t.end()
        });
});

tap.test('DELETE /students/s2/class', async (t: any) => {
    const request = await api.parseRequest([ 'students', 's2', 'class' ]);
    request.type = ApiRequestType.Delete;

    try {
        api.buildQuery(request);
        t.ok(false, "an invalid query should not succeed");
        t.end()
    }
    catch(e) {
        t.ok(e instanceof ApiEdgeError);
        t.equal(e.status, 400);
        t.equal(e.message, 'Invalid Delete Query');
        t.end()
    }
});

tap.test('PATCH /schools/s2', async (t: any) => {
    const request = await api.parseRequest([ 'schools', 's2' ]);
    request.type = ApiRequestType.Patch;
    request.body = {
        name: "Cool School"
    };
    const query = api.buildQuery(request);

    t.equal(query.steps.length, 4, 'should build a 4 step query');
    t.ok(query.steps[0] instanceof builder.SetBodyQueryStep, 'SET BODY');
    t.ok(query.steps[1] instanceof builder.ExtendContextLiveQueryStep, 'EXTEND');
    t.ok(query.steps[2] instanceof builder.ExtendContextQueryStep, 'APPLY PARAMS');
    t.ok(query.steps[3] instanceof builder.QueryEdgeQueryStep, 'QUERY');

    return query.execute()
        .then(resp => {
            t.same(resp.data,
                {
                    address: "12, Test street, North Pole, HA23535",
                    id: "s2",
                    name: "Cool School",
                    phone: "456345283"
                });
            t.equal(resp.metadata, null);
            t.end()
        })
        .catch(() => {
            t.ok(false, "a valid query should not fail");
            t.end()
        });
});

tap.test('PUT /schools/s2', async (t: any) => {
    const request = await api.parseRequest([ 'schools', 's2' ]);
    request.type = ApiRequestType.Update;
    request.body = {
        name: "Cool School"
    };
    const query = api.buildQuery(request);

    t.equal(query.steps.length, 4, 'should build a 4 step query');
    t.ok(query.steps[0] instanceof builder.SetBodyQueryStep, 'SET BODY');
    t.ok(query.steps[1] instanceof builder.ExtendContextLiveQueryStep, 'EXTEND');
    t.ok(query.steps[2] instanceof builder.ExtendContextQueryStep, 'APPLY PARAMS');
    t.ok(query.steps[3] instanceof builder.QueryEdgeQueryStep, 'QUERY');

    return query.execute()
        .then(resp => {
            t.same(resp.data,
                {
                    id: "s2",
                    name: "Cool School",
                    address: null,
                    phone: null
                });
            t.equal(resp.metadata, null);
            t.end()
        })
        .catch(() => {
            t.ok(false, "a valid query should not fail");
            t.end()
        });
});

tap.test('GET /students/s2/rename', async (t: any) => {
    const request = await api.parseRequest([ 'students', 's2', 'rename' ]);
    request.type = ApiRequestType.Read;

    try {
        api.buildQuery(request);
        t.ok(false, 'an invalid query should not succeed')
    }
    catch(e) {
        t.ok(e instanceof ApiEdgeError);
        t.equal(e.message, 'Method Not Allowed');
        t.equal(e.status, 405);
    }
    t.end()
});

tap.test('/students/s2/withHungarianName', async (t: any) => {
    const request = await api.parseRequest([ 'students', 's2', 'withHungarianName' ]),
        query = api.buildQuery(request);

    t.equal(query.steps.length, 5, 'should build an 5 step query');
    t.ok(query.steps[0] instanceof builder.ExtendContextLiveQueryStep, 'EXTEND');
    t.ok(query.steps[1] instanceof builder.QueryEdgeQueryStep, 'QUERY /students');
    t.ok(query.steps[2] instanceof builder.ExtendContextQueryStep, 'APPLY PARAMS');
    t.ok(query.steps[3] instanceof builder.ProvideIdQueryStep, 'PROVIDE ID');
    t.ok(query.steps[4] instanceof builder.CallMethodQueryStep, 'call{withHungarianName}');

    return query.execute()
        .then(resp => {
            t.same(resp.data, {
                id: "s2",
                fullName: "Dave Test",
                hungarianName: "Test Dave",
                email: "dave.test@gmail.com",
                schoolId: "s1",
                classId: "c1"
            });
            t.equal(resp.metadata, null);
            t.end()
        })
        .catch(() => {
            t.ok(false, "a valid query should not fail");
            t.end()
        });
});

tap.test('POST /students/s2/rename', async (t: any) => {
    const request = await api.parseRequest([ 'students', 's2', 'rename' ]);
    request.type = ApiRequestType.Update;
    request.body = { name: "Test David" };

    const query = api.buildQuery(request);

    t.equal(query.steps.length, 5, 'should build a 4 step query');
    t.ok(query.steps[0] instanceof builder.SetBodyQueryStep, 'SET BODY');
    t.ok(query.steps[1] instanceof builder.SetResponseQueryStep, 'SET RESPONSE');
    t.ok(query.steps[2] instanceof builder.ExtendContextQueryStep, 'APPLY PARAMS');
    t.ok(query.steps[3] instanceof builder.ProvideIdQueryStep, 'PROVIDE ID');
    t.ok(query.steps[4] instanceof builder.CallMethodQueryStep, 'call{rename}');

    return query.execute()
        .then(resp => {
            t.same(resp.data, {
                id: "s2",
                fullName: "David Test",
                email: "dave.test@gmail.com",
                schoolId: "s1",
                classId: "c1"
            });
            t.equal(resp.metadata, null);
            t.end()
        })
        .catch(() => {
            t.ok(false, "a valid query should not fail");
            t.end()
        });
});