import { IEventAggregationService } from '@studyportals/event-aggregation-service-interface';
import { ITokenBasedSessionService, SessionServiceReadyEvent } from '@studyportals/student-interfaces';
import { StudentField } from '@studyportals/studentdomain';
import { suite, test } from '@testdeck/mocha';
import * as Moq from 'typemoq';
import { Actor } from '../../../interfaces';
import { Deferred } from '../../../src/domain/deferred';
import { OfflineStudentRepositoryState } from '../../../src/domain/states/offline-student-repository-state';
import { OnlineStudentRepositoryState } from '../../../src/domain/states/online-student-repository-state';
import { PendingStudentRepositoryState } from '../../../src/domain/states/pending-student-repository-state';
import { StudentRepository } from '../../../src/domain/student-repository';

@suite()
class PendingStudentRepositoryStateTest {
	private testInstanceMock: Moq.IMock<PendingStudentRepositoryState>;

	private eventAggregationServiceMock: Moq.IMock<IEventAggregationService>;
	private sessionServiceMock: Moq.IMock<ITokenBasedSessionService>;
	private studentRepositoryMock: Moq.IMock<StudentRepository>;
	private lockMock: Moq.IMock<Deferred>;
	private offlineStudentRepositoryStateMock: Moq.IMock<OfflineStudentRepositoryState>;
	private onlineStudentRepositoryStateMock: Moq.IMock<OnlineStudentRepositoryState>;

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

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

	private get lock(): Deferred {
		return this.lockMock.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 testInstance(): PendingStudentRepositoryState {
		return this.testInstanceMock.object;
	}

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

		this.eventAggregationServiceMock = Moq.Mock.ofType<IEventAggregationService>();
		this.sessionServiceMock = Moq.Mock.ofType<ITokenBasedSessionService>();
		this.studentRepositoryMock = Moq.Mock.ofType<StudentRepository>();
		this.lockMock = Moq.Mock.ofType<Deferred>();

		this.offlineStudentRepositoryStateMock = Moq.Mock.ofType<OfflineStudentRepositoryState>();
		this.onlineStudentRepositoryStateMock = Moq.Mock.ofType<OnlineStudentRepositoryState>();

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

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

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

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

		this.studentRepositoryMock
			.setup((x) => x['offlineState'])
			.returns(() => this.offlineStudentRepositoryState);

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

	@test
	public notify_Should_Call_onSesionServiceReady_When_Called(): void {
		// given
		const providedSessionService = 'potato' as any;
		this.testInstanceMock.setup((x) => x['onSessionServiceReady'](Moq.It.isAny()));

		const providedEvent = new SessionServiceReadyEvent(providedSessionService);
		// when
		this.testInstance.notify(providedEvent);

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

	@test
	public async onSessionServiceReady_Should_Switch_State_To_Online_Sync_Offline_When_Session_Is_Not_Null(): Promise<void> {
		// given
		const providedSessionService = { getSession: () => 'something' as any } as any;

		// when
		await this.testInstance['onSessionServiceReady'](providedSessionService);

		// then
		this.studentRepositoryMock.verify((x) => x.updateState(this.onlineStudentRepositoryState), Moq.Times.once());
		this.lockMock.verify((x) => x.open(), Moq.Times.once());
		this.offlineStudentRepositoryStateMock.verify((x) => x.syncData(), Moq.Times.once());
	}

	@test
	public async onSessionServiceReady_Should_Switch_State_To_Offline_When_Session_Is_Null(): Promise<void> {
		// given
		const providedSessionService = { getSession: () => null } as any;

		// when
		await this.testInstance['onSessionServiceReady'](providedSessionService);

		// then
		this.studentRepositoryMock.verify((x) => x.updateState(this.offlineStudentRepositoryState), Moq.Times.once());
		this.lockMock.verify((x) => x.open(), Moq.Times.once());
	}

	@test
	public async setStudentData_Should_Await_Till_Lock_Opens_To_Call_State_When_Lock_Is_Closed(): Promise<void> {
		// given
		this.testInstance['state'] = this.offlineStudentRepositoryState;
		const expectedStudentData = 'hello I am student data' as any;
		const expectedActor = Actor.AUTOMATION;

		this.lockMock.setup((x) => x.untilOpen()).returns(
			async () => { return; },
		);

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

		// then
		this.lockMock.verify((x) => x.untilOpen(), Moq.Times.once());
		this.offlineStudentRepositoryStateMock.verify((x) => x.setStudentData(expectedStudentData, expectedActor), Moq.Times.once());
	}

	@test
	public async getStudentData_Should_Await_Till_Lock_Opens_To_Call_State_When_Lock_Is_Closed(): Promise<void> {
		// given
		this.testInstance['state'] = this.offlineStudentRepositoryState;
		const providedStudentData = 'hello I am student data' as any;

		this.lockMock.setup((x) => x.untilOpen()).returns(
			async () => { return; },
		);

		// when
		await this.testInstance.getStudentData(providedStudentData);

		// then
		this.lockMock.verify((x) => x.untilOpen(), Moq.Times.once());
		this.offlineStudentRepositoryStateMock.verify((x) => x.getStudentData(providedStudentData), Moq.Times.once());
	}

	@test
	public async addToCollection_Should_Await_Till_Lock_Opens_To_Call_State_When_Lock_Is_Closed(): Promise<void> {
		// given
		this.testInstance['state'] = this.offlineStudentRepositoryState;
		const providedDisciplines = 'hello I am disciplines' as any;

		this.lockMock.setup((x) => x.untilOpen()).returns(
			async () => { return; },
		);

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

		// then
		this.lockMock.verify((x) => x.untilOpen(), Moq.Times.once());
		this.offlineStudentRepositoryStateMock.verify((x) => x.addToCollection(StudentField.DISCIPLINES, providedDisciplines), Moq.Times.once());
	}

	@test
	public async removeFromCollection_Should_Await_Till_Lock_Opens_To_Call_State_When_Lock_Is_Closed(): Promise<void> {
		// given
		this.testInstance['state'] = this.offlineStudentRepositoryState;
		const providedDisciplines = 'hello I am disciplines' as any;

		this.lockMock.setup((x) => x.untilOpen()).returns(
			async () => { return; },
		);

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

		// then
		this.lockMock.verify((x) => x.untilOpen(), Moq.Times.once());
		this.offlineStudentRepositoryStateMock.verify((x) => x.removeFromCollection(StudentField.DISCIPLINES, providedDisciplines), Moq.Times.once());
	}
}
