import { IStudent, StudentField } from '@studyportals/studentdomain';
import { suite, test } from '@testdeck/mocha';
import * as Moq from 'typemoq';
import { IMock } from 'typemoq/Api/IMock';
import { Actor } from '../../../interfaces';
import { WriteHistoryRecord } from '../../../src/domain/dto/write-history-record';
import {
	OfflineToOnlineSynchronizationService,
} from '../../../src/domain/services/offline-to-online-synchronization-service';
import { OfflineStudentRepositoryState } from '../../../src/domain/states/offline-student-repository-state';
import { OnlineStudentRepositoryState } from '../../../src/domain/states/online-student-repository-state';

@suite
class OfflineToOnlineSynchronizationServiceTest {

	protected testInstanceMock: IMock<OfflineToOnlineSynchronizationService>;

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

	protected offlineStudentRepositoryStateMock: IMock<OfflineStudentRepositoryState>;

	protected get offlineStudentRepositoryState(): OfflineStudentRepositoryState {
		return this.offlineStudentRepositoryStateMock.object;
	}

	protected onlineStudentRepositoryStateMock: IMock<OnlineStudentRepositoryState>;

	protected get onlineStudentRepositoryState(): OnlineStudentRepositoryState {
		return this.onlineStudentRepositoryStateMock.object;
	}

	public before(): void {
		this.offlineStudentRepositoryStateMock = Moq.Mock.ofType<OfflineStudentRepositoryState>();
		this.onlineStudentRepositoryStateMock = Moq.Mock.ofType<OnlineStudentRepositoryState>();

		this.testInstanceMock = Moq.Mock.ofType(OfflineToOnlineSynchronizationService);
		this.testInstanceMock.callBase = true;

		this.testInstanceMock
			.setup((x) => x['offline'])
			.returns(() => this.offlineStudentRepositoryState);
		this.testInstanceMock
			.setup((x) => x['online'])
			.returns(() => this.onlineStudentRepositoryState);
	}

	@test
	public async syncData_Should_SyncNotProtectedNotEmptyOfflineDataToOnline_When_NoOnlineDataSet(): Promise<void> {
		// given
		const providedOfflineData = {
			[StudentField.FIRST_NAME]: 'myName',
			[StudentField.LAST_NAME]: 'myLastName',
			[StudentField.ACCOMPLISHMENTS]: undefined,
		};

		const providedOnlineData: IStudent = {};
		const providedWriteHistory = [];

		const expectedData: IStudent = {
			[StudentField.FIRST_NAME]: 'myName',
			[StudentField.LAST_NAME]: 'myLastName',
		};

		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOfflineData);
		this.onlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOnlineData);
		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getWriteHistory())
			.returns(async () => providedWriteHistory);

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

		// then
		this.onlineStudentRepositoryStateMock
			.verify(
				(x) => x.setStudentData(expectedData, Actor.USER),
				Moq.Times.once(),
			);
	}

	@test
	public async syncData_Should_SyncNotProtectedAndNotEmptyOfflineDataToOnline_When_OnlineDataSet(): Promise<void> {
		// given
		const providedOfflineData = {
			[StudentField.FIRST_NAME]: 'myName',
			[StudentField.LAST_NAME]: 'myLastName',
			[StudentField.ACCOMPLISHMENTS]: undefined,
		};

		const providedOnlineData: IStudent = {
			[StudentField.FIRST_NAME]: 'myOldName',
			[StudentField.LAST_NAME]: 'myLastOldName',
			[StudentField.ACCOMPLISHMENTS]: 'someAccomplishments',
		};
		const providedWriteHistory = [];

		const expectedData: IStudent = {
			[StudentField.FIRST_NAME]: 'myName',
			[StudentField.LAST_NAME]: 'myLastName',
		};

		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOfflineData);
		this.onlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOnlineData);
		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getWriteHistory())
			.returns(async () => providedWriteHistory);

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

		// then
		this.onlineStudentRepositoryStateMock
			.verify(
				(x) => x.setStudentData(expectedData, Actor.USER),
				Moq.Times.once(),
			);
	}

	@test
	public async syncData_Should_SyncProtectedOfflineDataToOnline_When_OnlineFieldsNotSet(): Promise<void> {
		// given
		const providedOfflineData: IStudent = {
			[StudentField.FIRST_NAME]: 'myName',
			[StudentField.NATIONALITY_COUNTRY_ID]: 82,
			[StudentField.NATIONALITY_COUNTRY_ISO]: 'US',
			[StudentField.ACCOMPLISHMENTS]: undefined,
		};

		const providedOnlineData: IStudent = {
			[StudentField.FIRST_NAME]: 'myOldName',
			[StudentField.ACCOMPLISHMENTS]: 'someAccomplishments',
		};

		const providedWriteHistory = [];

		const expectedData: IStudent = {
			[StudentField.FIRST_NAME]: 'myName',
			[StudentField.NATIONALITY_COUNTRY_ID]: 82,
			[StudentField.NATIONALITY_COUNTRY_ISO]: 'US',
		};

		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOfflineData);
		this.onlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOnlineData);
		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getWriteHistory())
			.returns(async () => providedWriteHistory);

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

		// then
		this.onlineStudentRepositoryStateMock
			.verify(
				(x) => x.setStudentData(expectedData, Actor.USER),
				Moq.Times.once(),
			);
	}

	@test
	public async syncData_Should_SyncProtectedOfflineDataToOnline_When_OnlineFieldsSetButHistoryWriterActorUser(): Promise<void> {
		// given
		const providedOfflineData: IStudent = {
			[StudentField.FIRST_NAME]: 'myName',
			[StudentField.NATIONALITY_COUNTRY_ID]: 82,
			[StudentField.NATIONALITY_COUNTRY_ISO]: 'US',
			[StudentField.ACCOMPLISHMENTS]: undefined,
		};

		const providedOnlineData: IStudent = {
			[StudentField.FIRST_NAME]: 'myOldName',
			[StudentField.ACCOMPLISHMENTS]: 'someAccomplishments',
			[StudentField.NATIONALITY_COUNTRY_ID]: 1,
			[StudentField.NATIONALITY_COUNTRY_ISO]: 'NL',
		};

		const providedWriteHistory: WriteHistoryRecord[] = [
			{field: StudentField.NATIONALITY_COUNTRY_ID, actor: Actor.USER},
			{field: StudentField.NATIONALITY_COUNTRY_ISO, actor: Actor.USER},
		];

		const expectedData: IStudent = {
			[StudentField.FIRST_NAME]: 'myName',
			[StudentField.NATIONALITY_COUNTRY_ID]: 82,
			[StudentField.NATIONALITY_COUNTRY_ISO]: 'US',
		};

		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOfflineData);
		this.onlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOnlineData);
		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getWriteHistory())
			.returns(async () => providedWriteHistory);

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

		// then
		this.onlineStudentRepositoryStateMock
			.verify(
				(x) => x.setStudentData(expectedData, Actor.USER),
				Moq.Times.once(),
			);
	}

	@test
	public async syncData_Should_NotSyncProtectedOfflineDataToOnline_When_OnlineFieldsSetAndWriterActorAutomation(): Promise<void> {
		// given
		const providedOfflineData: IStudent = {
			[StudentField.FIRST_NAME]: 'myName',
			[StudentField.NATIONALITY_COUNTRY_ID]: 82,
			[StudentField.NATIONALITY_COUNTRY_ISO]: 'US',
			[StudentField.ACCOMPLISHMENTS]: undefined,
		};

		const providedOnlineData: IStudent = {
			[StudentField.FIRST_NAME]: 'myOldName',
			[StudentField.ACCOMPLISHMENTS]: 'someAccomplishments',
			[StudentField.NATIONALITY_COUNTRY_ID]: 1,
			[StudentField.NATIONALITY_COUNTRY_ISO]: 'NL',
		};

		const providedWriteHistory: WriteHistoryRecord[] = [
			{field: StudentField.NATIONALITY_COUNTRY_ID, actor: Actor.AUTOMATION},
			{field: StudentField.NATIONALITY_COUNTRY_ISO, actor: Actor.AUTOMATION},
		];

		const expectedData: IStudent = {
			[StudentField.FIRST_NAME]: 'myName',
		};

		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOfflineData);
		this.onlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOnlineData);
		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getWriteHistory())
			.returns(async () => providedWriteHistory);

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

		// then
		this.onlineStudentRepositoryStateMock
			.verify(
				(x) => x.setStudentData(expectedData, Actor.USER),
				Moq.Times.once(),
			);
	}

	@test
	public async syncData_Should_SyncCorrectProtectedOfflineDataToOnline_When_OnlineFieldsSetAndWriterActorsMixed(): Promise<void> {
		// given
		const providedOfflineData: IStudent = {
			[StudentField.FIRST_NAME]: 'myName',
			[StudentField.NATIONALITY_COUNTRY_ID]: 82,
			[StudentField.NATIONALITY_COUNTRY_ISO]: 'US',
			[StudentField.ACCOMPLISHMENTS]: undefined,
		};

		const providedOnlineData: IStudent = {
			[StudentField.FIRST_NAME]: 'myOldName',
			[StudentField.ACCOMPLISHMENTS]: 'someAccomplishments',
			[StudentField.NATIONALITY_COUNTRY_ID]: 1,
			[StudentField.NATIONALITY_COUNTRY_ISO]: 'NL',
		};

		const providedWriteHistory: WriteHistoryRecord[] = [
			{field: StudentField.NATIONALITY_COUNTRY_ID, actor: Actor.AUTOMATION},
			{field: StudentField.NATIONALITY_COUNTRY_ISO, actor: Actor.USER},
		];

		const expectedData: IStudent = {
			[StudentField.FIRST_NAME]: 'myName',
			[StudentField.NATIONALITY_COUNTRY_ISO]: 'US',
		};

		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOfflineData);
		this.onlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOnlineData);
		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getWriteHistory())
			.returns(async () => providedWriteHistory);

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

		// then
		this.onlineStudentRepositoryStateMock
			.verify(
				(x) => x.setStudentData(expectedData, Actor.USER),
				Moq.Times.once(),
			);
	}

	@test
	public async syncData_Should_SyncProtectedOfflineDataToOnline_When_OnlineFieldsUndefinedAndWriterActorAutomation(): Promise<void> {
		// given
		const providedOfflineData: IStudent = {
			[StudentField.FIRST_NAME]: 'myName',
			[StudentField.NATIONALITY_COUNTRY_ID]: 82,
			[StudentField.NATIONALITY_COUNTRY_ISO]: 'US',
			[StudentField.ACCOMPLISHMENTS]: undefined,
		};

		const providedOnlineData: IStudent = {
			[StudentField.FIRST_NAME]: 'myOldName',
			[StudentField.ACCOMPLISHMENTS]: 'someAccomplishments',
			[StudentField.NATIONALITY_COUNTRY_ID]: undefined,
			[StudentField.NATIONALITY_COUNTRY_ISO]: undefined,
		};

		const providedWriteHistory: WriteHistoryRecord[] = [
			{field: StudentField.NATIONALITY_COUNTRY_ID, actor: Actor.AUTOMATION},
			{field: StudentField.NATIONALITY_COUNTRY_ISO, actor: Actor.AUTOMATION},
		];

		const expectedData: IStudent = {
			[StudentField.FIRST_NAME]: 'myName',
			[StudentField.NATIONALITY_COUNTRY_ID]: 82,
			[StudentField.NATIONALITY_COUNTRY_ISO]: 'US',
		};

		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOfflineData);
		this.onlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOnlineData);
		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getWriteHistory())
			.returns(async () => providedWriteHistory);

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

		// then
		this.onlineStudentRepositoryStateMock
			.verify(
				(x) => x.setStudentData(expectedData, Actor.USER),
				Moq.Times.once(),
			);
	}

	@test
	public async syncData_Should_SyncProtectedOfflineDataToOnline_When_OnlineFieldsSetAndNoRelevantWriteHistoryRecords(): Promise<void> {
		// given
		const providedOfflineData: IStudent = {
			[StudentField.FIRST_NAME]: 'myName',
			[StudentField.NATIONALITY_COUNTRY_ID]: 82,
			[StudentField.NATIONALITY_COUNTRY_ISO]: 'US',
			[StudentField.ACCOMPLISHMENTS]: undefined,
		};

		const providedOnlineData: IStudent = {
			[StudentField.FIRST_NAME]: 'myOldName',
			[StudentField.ACCOMPLISHMENTS]: 'someAccomplishments',
			[StudentField.NATIONALITY_COUNTRY_ID]: 1,
			[StudentField.NATIONALITY_COUNTRY_ISO]: 'NL',
		};

		const providedWriteHistory: WriteHistoryRecord[] = [
			{field: StudentField.AFFILIATE, actor: Actor.AUTOMATION},
			{field: StudentField.AFFILIATE_URL, actor: Actor.AUTOMATION},
		];

		const expectedData: IStudent = {
			[StudentField.FIRST_NAME]: 'myName',
			[StudentField.NATIONALITY_COUNTRY_ID]: 82,
			[StudentField.NATIONALITY_COUNTRY_ISO]: 'US',
		};

		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOfflineData);
		this.onlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOnlineData);
		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getWriteHistory())
			.returns(async () => providedWriteHistory);

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

		// then
		this.onlineStudentRepositoryStateMock
			.verify(
				(x) => x.setStudentData(expectedData, Actor.USER),
				Moq.Times.once(),
			);
	}

	@test
	public async syncData_Should_NotSyncAndNoBroadcast_When_NoOfflineData(): Promise<void> {
		// given
		const providedOfflineData: IStudent = {};

		const providedOnlineData: IStudent = {};

		const providedWriteHistory: WriteHistoryRecord[] = [
			{field: StudentField.AFFILIATE, actor: Actor.AUTOMATION},
			{field: StudentField.AFFILIATE_URL, actor: Actor.AUTOMATION},
		];

		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOfflineData);
		this.onlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOnlineData);
		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getWriteHistory())
			.returns(async () => providedWriteHistory);

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

		// then
		this.onlineStudentRepositoryStateMock
			.verify(
				(x) => x.setStudentData(Moq.It.isAny(), Moq.It.isAny()),
				Moq.Times.never(),
			);

	}

	@test
	public async syncData_Should_NotSync_When_OfflineDataSetButNoOverrideData(): Promise<void> {
		// given
		const providedOfflineData: IStudent = {
			[StudentField.CURRENCY]: 'EUR',
		};

		const providedOnlineData: IStudent = {
			[StudentField.CURRENCY]: 'USD',
		};

		const providedWriteHistory: WriteHistoryRecord[] = [
			{field: StudentField.CURRENCY, actor: Actor.AUTOMATION},
		];

		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOfflineData);
		this.onlineStudentRepositoryStateMock
			.setup((x) => x.getStudentData(Moq.It.isAny()))
			.returns(async () => providedOnlineData);
		this.offlineStudentRepositoryStateMock
			.setup((x) => x.getWriteHistory())
			.returns(async () => providedWriteHistory);

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

		// then
		this.onlineStudentRepositoryStateMock
			.verify(
				(x) => x.setStudentData(Moq.It.isAny(), Moq.It.isAny()),
				Moq.Times.never(),
			);
	}

}
