import { DataStorage } from '@studyportals/data-storage/dist/classes/DataStorage';
import { 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 { InterestType } from '../../interfaces/enumerations';
import { AnonymousStudentEventBroadcaster } from '../../src/infrastructure/anonymous-student-event-broadcaster';
import { LocalStudentClient } from '../../src/infrastructure/clients/local-student-client';

@suite
class LocalStudentClientTest {

	protected testInstanceMock: IMock<LocalStudentClient>;

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

	protected anonymousStudentEventBroadcasterMock: IMock<AnonymousStudentEventBroadcaster>;

	protected get anonymousStudentEventBroadcaster(): AnonymousStudentEventBroadcaster {
		return this.anonymousStudentEventBroadcasterMock.object;
	}

	protected localStorageMock: IMock<DataStorage>;

	protected get localStorage(): DataStorage {
		return this.localStorageMock.object;
	}

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

		this.anonymousStudentEventBroadcasterMock = Moq.Mock.ofType<AnonymousStudentEventBroadcaster>();
		this.localStorageMock = Moq.Mock.ofType<DataStorage>();

		this.testInstanceMock
			.setup((x) => x['anonymousStudentEventBroadcaster'])
			.returns(() => this.anonymousStudentEventBroadcaster);

		this.testInstanceMock
			.setup((x) => x['localStorage'])
			.returns(() => this.localStorage);
	}

	@test
	public async cleanUp_Should_Call_Remove_On_LocalStorage_When_Called(): Promise<void> {
		// given
		const givenStorageKey = 'myKey';
		this.testInstanceMock.setup((x) => x['storageKey']).returns(() => givenStorageKey);

		// when
		await this.testInstance.cleanUp();

		// then
		this.localStorageMock.verify((x) => x.remove(givenStorageKey), Moq.Times.once());
	}

	@test
	public async getData_Should_Only_Return_Request_Fields_When_Called(): Promise<void> {
		// given
		const givenFields = [StudentField.BIRTH_DATE, StudentField.ACCOMPLISHMENTS];
		const givenData = {
			[StudentField.BIRTH_DATE]: '12-12-2020',
			[StudentField.ACCOMPLISHMENTS]: 'Mate, i wasnt even born.',
		};

		this.testInstanceMock
			.setup((x) => x['retrieveDataFromLocalStorage']())
			.returns(() => givenData);

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

		// then
		assert.deepEqual(result, givenData);
	}

	@test
	public async getData_Should_Only_Return_Request_Fields_When_Already_Saved_Fields_Are_Given(): Promise<void> {
		// given
		const givenFields = [StudentField.BIRTH_DATE, StudentField.ACCOMPLISHMENTS];
		const givenData = {
			[StudentField.BIRTH_DATE]: '12-12-2020',
		};

		this.testInstanceMock
			.setup((x) => x['retrieveDataFromLocalStorage']())
			.returns(() => givenData);

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

		// then
		assert.isFalse(Object.keys(result).includes(StudentField.ACCOMPLISHMENTS));
	}

	@test
	public async setData_Should_Merge_Existing_AND_New_Data_When_Called_With_Data(): Promise<void> {
		// given
		const givenData = {[StudentField.FIRST_NAME]: 'Harry', [StudentField.LAST_NAME]: 'NonOfYourBusiness'};
		const existingData = {[StudentField.FIRST_NAME]: 'Henk', [StudentField.EMAIL]: 'myEmail@email.com'};
		const expectedData = {
			[StudentField.EMAIL]: existingData[StudentField.EMAIL],
			[StudentField.FIRST_NAME]: givenData[StudentField.FIRST_NAME],
			[StudentField.LAST_NAME]: givenData[StudentField.LAST_NAME],
		};

		this.testInstanceMock
			.setup((x) => x['retrieveDataFromLocalStorage']())
			.returns(() => existingData);

		this.testInstanceMock.setup((x) => x['setDataInLocalStorage'](Moq.It.isAny()));
		this.testInstanceMock.setup((x) => x['broadcastChanges'](Moq.It.isAny()));

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

		// then

		this.testInstanceMock.verify((x) => x['setDataInLocalStorage'](expectedData), Moq.Times.once());
	}

	@test
	public async setData_Should_Broadcast_Given_Changes_When_Called(): Promise<void> {
		// given
		const givenData = {[StudentField.FIRST_NAME]: 'Harry', [StudentField.LAST_NAME]: 'NonOfYourBusiness'};

		this.testInstanceMock
			.setup((x) => x['retrieveDataFromLocalStorage']())
			.returns(() => {});

		this.testInstanceMock.setup((x) => x['setDataInLocalStorage'](Moq.It.isAny()));
		this.testInstanceMock.setup((x) => x['broadcastChanges'](Moq.It.isAny()));

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

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

	@test
	public async addToCollection_Should_Call_AddToIdList_With_Correct_Ids_And_Key_When_Called(): Promise<void> {
		// given
		const expectedKey = StudentField.DISCIPLINES;
		const givenIds = [1, 2, 5, 6];

		this.testInstanceMock.setup((x) => x['addToIdListInLocalStorage'](Moq.It.isAny(), Moq.It.isAny()));

		// when
		await this.testInstance.addToCollection(expectedKey, givenIds);

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

	@test
	public async removeFromCollection_Should_Call_RemoveFromIdList_With_Correct_Ids_And_Key_When_Called(): Promise<void> {
		// given
		const expectedKey = StudentField.DISCIPLINES;
		const givenIds = [1, 2, 5, 6];

		this.testInstanceMock.setup((x) => x['addToIdListInLocalStorage'](Moq.It.isAny(), Moq.It.isAny()));

		// when
		await this.testInstance.removeFromCollection(expectedKey, givenIds);

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

	@test
	public async retrieveDataFromLocalStorage_Should_Return_Parsed_Object_When_Retrieving_A_Saved_Stringified_Object(): Promise<void> {
		// given
		const givenData = {[StudentField.FIRST_NAME]: 'Henk'};
		this.localStorageMock
			.setup((x) => x.retrieve(Moq.It.isAny()))
			.returns(() => JSON.stringify(givenData));

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

		// then
		assert.deepEqual(result, givenData);
	}

	@test
	public async retrieveDataFromLocalStorage_Should_Return_Empty_Object_When_No_Saved_Data(): Promise<void> {
		// given
		const givenData = undefined;
		this.localStorageMock
			.setup((x) => x.retrieve(Moq.It.isAny()))
			.returns(() => givenData);

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

		// then
		assert.deepEqual(result, {});
	}
}
