import { ITokenBasedSessionService } from '@studyportals/student-interfaces';
import { IStudent, StudentField } from '@studyportals/studentdomain';
import config from 'config';
import { LastStateChangeHash, StudentClient } from '../interfaces/student-client';

export class StudentAPIClient implements StudentClient {

	constructor(private sessionService: ITokenBasedSessionService) {
	}

	private requestMap: Map<StudentField, Promise<any>> = new Map<StudentField, Promise<any>>();

	private get fetch(): any {
		return fetch;
	}

	public async getData(studentFields: StudentField[]): Promise<IStudent> {
		const actions = this.getActiveDataRequestsAndFieldsToRequest(studentFields);

		const activeDataRequests = actions.activeDataRequests;
		const studentFieldsToRequest = actions.studentFieldsToRequest;

		if (studentFieldsToRequest.length > 0) {
			activeDataRequests.push(this.retrieveStudentFields(studentFieldsToRequest));
		}

		return this.mergeStudentDataResponses(await Promise.all(activeDataRequests));
	}

	private async patchData(body: object): Promise<LastStateChangeHash> {
		const request = await fetch(`${config.STUDENTAPI_BASE_URL}/data`, {
			method: 'PATCH',
			headers: {
				Authorization: await this.getAccessToken(),
			},
			body: JSON.stringify(body),
		});

		if (!request.ok) {
			throw new Error('Failed to patch student data');
		}

		const responseBody = await request.json();
		return responseBody[StudentField.LAST_STATE_CHANGE_HASH];
	}

	public setData(studentData: IStudent): Promise<LastStateChangeHash> {
		return this.patchData(studentData);
	}

	public async addToCollection(type: StudentField, items: any[]): Promise<string> {
		return this.patchData({
			[`${type}_add`]: items,
		});
	}

	public async removeFromCollection(type: StudentField, items: any[]): Promise<string> {
		return this.patchData({
			[`${type}_remove`]: items,
		});
	}

	private getActiveDataRequestsAndFieldsToRequest(studentFields: StudentField[]): { activeDataRequests: Array<Promise<IStudent>>, studentFieldsToRequest: StudentField[] } {
		const activeDataRequests: Array<Promise<IStudent>> = [];
		const studentFieldsToRequest: StudentField[] = [];

		studentFields.forEach((field) => {
			const request = this.requestMap.get(field);

			if (request) {
				activeDataRequests.push(request);
				return;
			}

			studentFieldsToRequest.push(field);
		});

		return {activeDataRequests, studentFieldsToRequest};
	}

	private async retrieveStudentFields(studentFields: StudentField[]): Promise<IStudent> {
		const request = this.requestStudentData(studentFields);

		studentFields.forEach((field) => {
			this.requestMap.set(field, request);
		});

		const response = await request;

		studentFields.forEach((field) => {
			this.requestMap.delete(field);
		});

		return response;

	}

	private mergeStudentDataResponses(responses: IStudent[]): IStudent {
		let data: IStudent = {};
		responses.forEach((response) => {
			data = {...data, ...response};
		});

		return data;
	}

	private async requestStudentData(studentFields: StudentField[]): Promise<IStudent> {
		const request = await fetch(`${config.STUDENTAPI_BASE_URL}/data?scope=${studentFields.join(',')}`, {
			headers: {
				Authorization: await this.getAccessToken(),
			},
		});

		if (!request.ok) {
			throw new Error('Failed to retrieve data from StudentAPI');
		}

		const data = await request.json();
		Object.keys(data).forEach((key) => {
			if (data[key] === null) {
				data[key] = undefined;
			}
		});

		return data;
	}

	private async getAccessToken(): Promise<string> {
		const session = await this.sessionService.getSession();
		return session!.getAccessToken();
	}

}
