import { IEventAggregationService, ISubscriber } from "@studyportals/event-aggregation-service-interface";
import { WebsocketServiceReadyEvent } from '@studyportals/student-interfaces';
import { IStudent, StudentField } from '@studyportals/studentdomain';
import { StudentRepositoryStateType } from '../../../interfaces/enumerations';
import { AnonymousStudentProfileUpdated } from '../../../interfaces/events';
import { CatchReportAsyncException } from '../../decorators/error-decorators';
import { AnonymousStudentEventBroadcaster } from '../anonymous-student-event-broadcaster';
import { LastStateChangeHash, StudentClient } from '../interfaces/student-client';
import { IStudentProfileChanged } from '../student-profile-changed.inferface';
import { LocalStudentClient } from './local-student-client';
import { StudentAPIClient } from './student-api-client';

export class CachedStudentClient implements StudentClient, ISubscriber<WebsocketServiceReadyEvent | IStudentProfileChanged> {
	private localStateHashes: LastStateChangeHash[];

	constructor(
		private studentAPIClient: StudentAPIClient,
		private localStudentClient: LocalStudentClient,
		private eventAggregationService: IEventAggregationService,
		private anonymousStudentEventBroadcaster: AnonymousStudentEventBroadcaster
	) {
		this.eventAggregationService.subscribeTo(WebsocketServiceReadyEvent.EventType, this, true);
		this.localStateHashes = [];
	}

	@CatchReportAsyncException
	public async notify(event: WebsocketServiceReadyEvent | IStudentProfileChanged): Promise<void> {
		if (event.eventType === WebsocketServiceReadyEvent.EventType) {
			this.handleWebsocketServiceReadyEvent(event as WebsocketServiceReadyEvent);
		}

		if (event.eventType === 'StudentProfileChanged') {
			await this.handleWebsocketEvent(event as IStudentProfileChanged);
		}
	}

	private handleWebsocketServiceReadyEvent(event: WebsocketServiceReadyEvent): void {
		event.webSocketService.subscribeToWebSocketEvent('StudentProfileChanged', this);
	}

	private async handleWebsocketEvent(event: IStudentProfileChanged): Promise<void> {
		const changes: IStudent = this.transformStudentProfileChangedEventIntoStudentData(event);
		const timestamp: Date = new Date(event.eventTimestamp);
		const isLocalUpdate: boolean = this.localStateHashes.includes(event.updated?.[StudentField.LAST_STATE_CHANGE_HASH]);

		await this.updateDataFromEvent(changes);

		const anonymousEvent = new AnonymousStudentProfileUpdated(
			timestamp,
			StudentRepositoryStateType.ONLINE,
			changes,
			isLocalUpdate
		);

		this.anonymousStudentEventBroadcaster.broadcastProfileUpdatedEvent(anonymousEvent);
	}

	private isForceUpdateField(studentField: StudentField): boolean {
		return [
			StudentField.DISCIPLINES,
			StudentField.INTERESTS_COUNTRIES,
			StudentField.INTERESTS_DISCIPLINES,
			StudentField.TUITION_BUDGET,
			StudentField.LIVING_BUDGET,
			StudentField.ATTENDANCE
		].includes(studentField);
	}

	private async updateDataFromEvent(studentData: IStudent): Promise<void> {
		const studentFieldsWithPartialData: StudentField[] = [];

		Object.keys(studentData).forEach((studentField: StudentField) => {
			if (studentData[studentField] instanceof Object || this.isForceUpdateField(studentField)) {
				studentFieldsWithPartialData.push(studentField);
			}
		});

		const studentDataForObjects = await this.studentAPIClient.getData(studentFieldsWithPartialData);
		await this.localStudentClient.setData({ ...studentData, ...studentDataForObjects });
	}

	private transformStudentProfileChangedEventIntoStudentData(event: IStudentProfileChanged): IStudent {
		const changes: IStudent = {};

		if (event.updated) {
			Object.keys(event.updated).forEach((key) => {
				changes[key] = event.updated[key];
			});
		}

		if (event.deleted) {
			Object.keys(event.deleted).forEach((key) => {
				changes[key] = undefined;
			});
		}

		return changes;
	}

	public async addToCollection(type: StudentField, items: any[]): Promise<void> {
		const lastStateChangeHash = await this.studentAPIClient.addToCollection(type, items);
		if (lastStateChangeHash) {
			this.localStateHashes.push(lastStateChangeHash);
		}
		await this.localStudentClient.addToCollection(type, items);
	}

	public async removeFromCollection(type: StudentField, items: any[]): Promise<void> {
		const lastStateChangeHash = await this.studentAPIClient.removeFromCollection(type, items);
		if (lastStateChangeHash) {
			this.localStateHashes.push(lastStateChangeHash);
		}
		await this.localStudentClient.removeFromCollection(type, items);
	}

	public async getData(studentFields: StudentField[]): Promise<IStudent> {
		const localData = await this.localStudentClient.getData(studentFields);
		const localKeys = Object.keys(localData);

		const missingFields = studentFields.filter((field) => {
			return !localKeys.includes(field);
		});

		if (missingFields.length > 0) {
			const remoteData = await this.studentAPIClient.getData(missingFields);
			await this.localStudentClient.setData(remoteData);
			return { ...localData, ...remoteData };
		}

		return localData;
	}

	public async setData(studentData: IStudent): Promise<void> {
		const lastStateChangeHash = await this.studentAPIClient.setData(studentData);
		if (lastStateChangeHash) {
			this.localStateHashes.push(lastStateChangeHash);
		}

		await this.localStudentClient.setData(studentData);
	}

	public clearCache(): void {
		return this.localStudentClient.cleanUp();
	}

}
