import { IEventAggregationService } from "@studyportals/event-aggregation-service-interface";
import { ITokenBasedSessionService,	SessionCreatedEvent } from '@studyportals/student-interfaces';
import { AttendanceType, IStudent, StudentField } from '@studyportals/studentdomain';
import { suite, test } from '@testdeck/mocha';
import { assert } from 'chai';
import { AnonymousStudentEventBroadcaster } from 'src/infrastructure/anonymous-student-event-broadcaster';
import * as Moq from 'typemoq';
import { Actor, StudentRepositoryStateType } from '../../../interfaces';
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';
import { StudentRepository } from '../../../src/domain/student-repository';
import { LocalStudentClient } from '../../../src/infrastructure/clients/local-student-client';

@suite()
class OfflineStudentRepositoryStateTest {
	private testInstance: OfflineStudentRepositoryState;

	private eventAggregationServiceMock: Moq.IMock<IEventAggregationService>;
	private sessionServiceMock: Moq.IMock<ITokenBasedSessionService>;
	private studentRepositoryMock: Moq.IMock<StudentRepository>;
	private onlineStudentRepositoryStateMock: Moq.IMock<OnlineStudentRepositoryState>;
	private studentClientMock: Moq.IMock<LocalStudentClient>;
	private anonymousStudentEventBroadcasterMock: Moq.IMock<AnonymousStudentEventBroadcaster>;
	private offlineToOnlineSynchronizationServiceMock: Moq.IMock<OfflineToOnlineSynchronizationService>;

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

	private get studentClient(): LocalStudentClient {
		return this.studentClientMock.object;
	}

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

	private get studentRepository(): StudentRepository {
		return this.studentRepositoryMock.object;
	}

	private get sessionService(): ITokenBasedSessionService {
		return this.sessionServiceMock.object;
	}

	private get eventAggregationService(): IEventAggregationService {
		return this.eventAggregationServiceMock.object;
	}

	private get offlineToOnlineSynchronizationService(): OfflineToOnlineSynchronizationService {
		return this.offlineToOnlineSynchronizationServiceMock.object;
	}

	public before(): void {
		this.eventAggregationServiceMock = Moq.Mock.ofType<IEventAggregationService>();
		this.sessionServiceMock = Moq.Mock.ofType<ITokenBasedSessionService>();
		this.studentRepositoryMock = Moq.Mock.ofType<StudentRepository>();
		this.onlineStudentRepositoryStateMock = Moq.Mock.ofType<OnlineStudentRepositoryState>();
		this.studentClientMock = Moq.Mock.ofType<LocalStudentClient>();
		this.anonymousStudentEventBroadcasterMock = Moq.Mock.ofType<AnonymousStudentEventBroadcaster>();
		this.offlineToOnlineSynchronizationServiceMock = Moq.Mock.ofType<OfflineToOnlineSynchronizationService>();

		this.testInstance = new OfflineStudentRepositoryState(this.eventAggregationService, this.sessionService, this.studentRepository);

		this.testInstance['studentClient'] = this.studentClient;
		this.testInstance['anonymousStudentEventBroadcaster'] = this.anonymousStudentEventBroadcaster;

		this.studentRepositoryMock
			.setup((x) => x['offlineToOnlineSynchronizationService'])
			.returns(() => this.offlineToOnlineSynchronizationService);
	}

	@test
	public initialize_Should_SubscribeToSessionCreatedEvent_When_Called(): void {
		// given
		this.eventAggregationServiceMock.setup((x) => x.subscribeTo(SessionCreatedEvent.EventType, Moq.It.isAny()));

		// when
		this.testInstance.initialize();

		// then
		this.eventAggregationServiceMock
			.verify((x) => x.subscribeTo(SessionCreatedEvent.EventType, Moq.It.isAny()), Moq.Times.once());
	}

	@test
	public async notify_Should_Sync_And_SwitchState_And_Broadcast_When_Called(): Promise<void> {
		// given
		const expectedOnlineSessionState = 'myOnlineSessionState' as any;
		const sessionCreatedEvent = {session: {}} as any;

		this.studentRepositoryMock
			.setup((x) => x.onlineState)
			.returns(() => expectedOnlineSessionState);

		// when
		await this.testInstance.notify(sessionCreatedEvent);

		// then
		this.studentRepositoryMock
			.verify(
				(x) => x.updateState(expectedOnlineSessionState),
				Moq.Times.once(),
			);

		this.offlineToOnlineSynchronizationServiceMock
			.verify(
				(x) => x.syncData(),
				Moq.Times.once(),
			);

		this.studentClientMock
			.verify(
				(x) => x.cleanUp(),
				Moq.Times.once(),
			);

		this.anonymousStudentEventBroadcasterMock
			.verify(
				(x) => x.broadcastStudentProfileSyncedEvent(Moq.It.isObjectWith({state: StudentRepositoryStateType.ONLINE} as any)),
				Moq.Times.once(),
			);
	}

	@test
	public async setStudentData_Should_Call_StudentClient_setData_When_Called(): Promise<void> {
		// given
		const expectedData: IStudent = {[StudentField.NATIONALITY_COUNTRY_ID]: 1};
		this.studentClientMock.setup((x) => x.setData(Moq.It.isAny()));

		// when
		await this.testInstance.setStudentData(expectedData, Actor.USER);

		// then
		this.studentClientMock.verify((x) => x.setData(expectedData), Moq.Times.once());
	}

	@test
	public async setStudentData_Should_Call_StudentClient_AddToWriteHistory_When_Called(): Promise<void> {
		// given
		const expectedField = StudentField.CURRENCY;
		const expectedActor = Actor.AUTOMATION;

		const providedData: IStudent = {[expectedField]: 'EUR'};

		this.studentClientMock.setup((x) => x.setData(Moq.It.isAny()));
		this.studentClientMock.setup((x) => x.addToWriteHistory(Moq.It.isAny(), Moq.It.isAny()));

		// when
		await this.testInstance.setStudentData(providedData, expectedActor);

		// then
		this.studentClientMock
			.verify(
				(x) => x.addToWriteHistory([expectedField], expectedActor),
				Moq.Times.once(),
			);
	}

	@test
	public getStudentData_Should_Call_StudentClient_getData_When_Called(): void {
		// given
		this.studentClientMock.setup((x) => x.getData(Moq.It.isAny()));

		// when
		this.testInstance.getStudentData(Moq.It.isAny());

		// then
		this.studentClientMock.verify((x) => x.getData(Moq.It.isAny()), Moq.Times.once());
	}

	@test
	public async addToCollection_Should_Call_StudentRepository_addToCollection_When_Called(): Promise<void> {
		// given
		this.studentClientMock.setup((x) => x.addToCollection(Moq.It.isAny(), Moq.It.isAny()));

		// when
		await this.testInstance.addToCollection(StudentField.ATTENDANCE, [AttendanceType.ONLINE]);

		// then
		this.studentClientMock.verify((x) => x.addToCollection(Moq.It.isAny(), Moq.It.isAny()), Moq.Times.once());
	}

	@test
	public async removeFromCollection_Should_Call_StudentRepository_addToCollection_When_Called(): Promise<void> {
		// given
		this.studentClientMock.setup((x) => x.removeFromCollection(Moq.It.isAny(), Moq.It.isAny()));

		// when
		await this.testInstance.removeFromCollection(StudentField.INTERESTS_COUNTRIES, [1, 4, 76]);

		// then
		this.studentClientMock.verify((x) => x.removeFromCollection(Moq.It.isAny(), Moq.It.isAny()), Moq.Times.once());
	}

	@test
	public async getWriteHistory_Should_ReturnCorrectWriteHistory_When_Called(): Promise<void> {
		// given
		const expectedWriteHistory = 'someHistory' as any;

		this.studentClientMock
			.setup((x) => x.getWriteHistory())
			.returns(() => expectedWriteHistory);

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

		// then
		assert.equal(result, expectedWriteHistory);
	}
}
