import { AttendanceType, StudentField } from '@studyportals/studentdomain';
import { suite, test } from '@testdeck/mocha';
import { assert } from 'chai';
import * as Moq from 'typemoq';
import { IMock } from 'typemoq/Api/IMock';
import { StudentAPIClient } from '../../src/infrastructure/clients/student-api-client';

@suite
class StudentAPIClientTest {

	protected testInstanceMock: IMock<StudentAPIClient>;

	protected get testInstance(): StudentAPIClient {
		return this.testInstanceMock.object;
	}

	public before(): void {
		this.testInstanceMock = Moq.Mock.ofType(StudentAPIClient);
		this.testInstanceMock.callBase = true;
	}

	@test
	public async getData_Should_Start_Request_When_No_Existing_Requests_Are_Open(): Promise<void> {
		// given
		const givenFields = [StudentField.FIRST_NAME, StudentField.LAST_NAME];
		const studentData = {[StudentField.FIRST_NAME]: 'Harry', [StudentField.LAST_NAME]: 'Jones'};

		this.testInstanceMock
			.setup((x) => x['retrieveStudentFields'](Moq.It.isAny()))
			.returns(async () => studentData);

		// when
		const result = await this.testInstance.getData(givenFields);

		// then
		this.testInstanceMock.verify((x) => x['retrieveStudentFields'](givenFields), Moq.Times.once());
		assert.deepEqual(result, studentData);
	}

	@test
	public async getData_Should_Start_Request_With_Partial_Fields_When_Some_Field_Requests_Are_Open(): Promise<void> {
		// given
		const givenFields = [StudentField.FIRST_NAME, StudentField.LAST_NAME];
		const studentDataOne = {[StudentField.LAST_NAME]: 'Jones'} as any;
		const studentDataTwo = {[StudentField.FIRST_NAME]: 'Harry'};

		this.testInstanceMock
			.setup((x) => x['retrieveStudentFields'](Moq.It.isAny()))
			.returns(async () => studentDataTwo);

		this.testInstance['requestMap'].set(StudentField.LAST_NAME, studentDataOne);

		// when
		const result = await this.testInstance.getData(givenFields);

		// then
		this.testInstanceMock.verify((x) => x['retrieveStudentFields']([StudentField.FIRST_NAME]), Moq.Times.once());
		assert.deepEqual(result, {...studentDataOne, ...studentDataTwo});
	}

	@test
	public async getData_Should_Not_Do_Any_Requests_When_All_Fields_Are_In_Existing_Request(): Promise<void> {
		// given
		const givenFields = [StudentField.FIRST_NAME, StudentField.LAST_NAME];
		const studentDataOne = {[StudentField.FIRST_NAME]: 'Harry'} as any;
		const studentDataTwo = {[StudentField.LAST_NAME]: 'Jones'} as any;

		this.testInstanceMock
			.setup((x) => x['retrieveStudentFields'](Moq.It.isAny()))
			.returns(async () => studentDataTwo);

		this.testInstance['requestMap'].set(StudentField.FIRST_NAME, studentDataOne);
		this.testInstance['requestMap'].set(StudentField.LAST_NAME, studentDataTwo);

		// when
		const result = await this.testInstance.getData(givenFields);

		// then
		this.testInstanceMock.verify((x) => x['retrieveStudentFields'](Moq.It.isAny()), Moq.Times.never());
		assert.deepEqual(result, {...studentDataOne, ...studentDataTwo});
	}

	@test
	public async setData_Should_Call_PatchData_With_Given_StudentData_When_Called(): Promise<void> {
		// given
		const studentData = {[StudentField.LAST_LOGIN_UTC]: 'someTime'} as any;
		this.testInstanceMock.setup((x) => x['patchData'](Moq.It.isAny()));

		// when
		await this.testInstance.setData(studentData);

		// then
		this.testInstanceMock.verify((x) => x['patchData'](studentData), Moq.Times.once());
	}

	@test
	public async addToCollection_Should_Call_PatchData_With_Given_Correct_Data_When_Called(): Promise<void> {
		// given
		const ids = [5, 4, 3];
		const expectedData = {
			disciplines_add: ids,
		} as any;
		this.testInstanceMock.setup((x) => x['patchData'](Moq.It.isAny()));

		// when
		await this.testInstance.addToCollection(StudentField.DISCIPLINES, ids);

		// then
		this.testInstanceMock.verify((x) => x['patchData'](expectedData), Moq.Times.once());

	}

	@test
	public async addToCollection_Attendance_Should_Call_PatchData_With_Given_Correct_Data_When_Called(): Promise<void> {
		// given
		const items = [AttendanceType.ONLINE, AttendanceType.BLENDED];
		const expectedData = {
			[`${StudentField.ATTENDANCE}_add`]: items,
		} as any;
		this.testInstanceMock.setup((x) => x['patchData'](Moq.It.isAny()));

		// when
		await this.testInstance.addToCollection(StudentField.ATTENDANCE, items);

		// then
		this.testInstanceMock.verify((x) => x['patchData'](expectedData), Moq.Times.once());

	}

	@test
	public async removeFromCollection_Attendance_Should_Call_PatchData_With_Given_Correct_Data_When_Called(): Promise<void> {
			// given
			const items = [AttendanceType.ONLINE, AttendanceType.BLENDED];
			const expectedData = {
				[`${StudentField.ATTENDANCE}_remove`]: items,
			} as any;
			this.testInstanceMock.setup((x) => x['patchData'](Moq.It.isAny()));

			// when
			await this.testInstance.removeFromCollection(StudentField.ATTENDANCE, items);

			// then
			this.testInstanceMock.verify((x) => x['patchData'](expectedData), Moq.Times.once());

		}

	@test
	public async removeFromCollection_Should_Call_PatchData_With_Given_Correct_Data_When_Called(): Promise<void> {
		// given
		const ids = [5, 4, 3];
		const expectedData = {
			disciplines_remove: ids,
		} as any;
		this.testInstanceMock.setup((x) => x['patchData'](Moq.It.isAny()));

		// when
		await this.testInstance.removeFromCollection(StudentField.DISCIPLINES, ids);

		// then
		this.testInstanceMock.verify((x) => x['patchData'](expectedData), Moq.Times.once());

	}

	@test
	public async getActiveDataRequestsAndFieldsToRequest_Should_Return_Correct_Active_Requests_And_Missing_Fields_When_Called(): Promise<void> {
		// given
		const fields = [StudentField.FIRST_NAME, StudentField.LAST_LOGIN_UTC, StudentField.EMAILING_FAVOURITES];
		const expectedSavedValue = 'ofc' as any;
		this.testInstance['requestMap'].set(StudentField.EMAILING_FAVOURITES, expectedSavedValue);

		// when
		const result = this.testInstance['getActiveDataRequestsAndFieldsToRequest'](fields);

		// then
		assert.deepEqual(result.studentFieldsToRequest, [StudentField.FIRST_NAME, StudentField.LAST_LOGIN_UTC]);
		assert.equal(await result.activeDataRequests[0], expectedSavedValue);
	}

	@test
	public async mergeStudentDataResponses_Should_Merge_All_Given_Objects_When_Multiple_Objects_Given(): Promise<void> {
		// given
		const givenList = [
			{[StudentField.FIRST_NAME]: 'Henk'},
			{[StudentField.LAST_NAME]: 'Yolo'},
			{[StudentField.EMAIL]: 'MySecretEmail@email.com'},
		];

		// when
		const result = this.testInstance['mergeStudentDataResponses'](givenList);

		// then
		assert.deepEqual(result, {...givenList[0], ...givenList[1], ...givenList[2]});
	}

}
