// Questo file fa parte di MyTown.
// MyTown è software libero: puoi ridistribuirlo e/o modificarlo secondo
// i termini della GNU Affero General Public License come pubblicata dalla
// Free Software Foundation, versione 3.
// Distribuito senza ALCUNA GARANZIA; vedi la GNU AGPL v3 per i dettagli.
// Copyright (C) Kondor Consulting & Services S.r.l.
// SPDX-License-Identifier: AGPL-3.0-or-later

import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { HttpRequest, HttpHandler, HttpInterceptor, HttpErrorResponse, HttpEvent, HttpHeaders } from '@angular/common/http';
import { catchError, finalize, map, switchMap } from 'rxjs/operators';
import { throwError } from 'rxjs/internal/observable/throwError';
import { ConfigService } from './config.service';
import { Observable, from } from 'rxjs';

@Injectable()
export class HttpRequestInterceptor implements HttpInterceptor {
	timer: any;
	http: any;

	wait_until_refreshtoken_done = false;

	refreshTokenPromise: any = null;

	constructor(private configService: ConfigService, private route: Router) {}

	intercept(req: HttpRequest<any>, next: HttpHandler): Observable<any> {
		if (this.timer) {
			clearTimeout(this.timer);
		}
		return from(this.createAuthorizationHeaders(req)).pipe(
			switchMap((headers: HttpHeaders) => {
				const headerObj: { [name: string]: string | string[] } = {};
				headers.keys().forEach((key: any) => {
					headerObj[key] = headers.get(key) as string;
				});
				const modifiedReq = req.clone({ setHeaders: headerObj });
				return next.handle(modifiedReq).pipe(
					catchError((error: HttpErrorResponse) => {
						let msgError = 'Qualcosa è andato storto, riprova più tardi';
						if (error.status === 0 || error.status === 404) {
							msgError = 'Servizio non raggiungibile';
							this.configService.presentToastError(msgError);
							return throwError(() => msgError);
						}

						if (error.status === 500) {
							msgError = 'Errore servizio interno';
							this.configService.presentToastError(msgError);
							return throwError(() => msgError);
						}

						msgError = error.error;
						// Return an observable with a user-facing error message.
						//return throwError(() => new Error('Something bad happened; please try again later.'));
						console.error('msgError', msgError);

						this.configService.presentToastError(msgError);

						if (error.status == 401) {
							/*if (this.config.isApp) {*/
							//Invia refresh token app mobile
							//console.log("Invia refresh token app mobile");
							/*if (error.message == "JWT token is expired") {
                                        return new Observable<HttpEvent<any>>();
                                    } else {
                                        localStorage.clear();
                                        this.route.navigate(['']);
                                    }
                                } else {*/

							localStorage.clear();
							this.route.navigate(['login']);
							return throwError(() => msgError);
						}

						if (error.status == 403) {
							localStorage.clear();
							this.route.navigate(['login']);
						}

						return throwError(() => msgError);
					}),
					finalize(() => {
						if (this.timer) {
							clearTimeout(this.timer);
						}
					}),
				);
			}),
		);
	}

	private async createAuthorizationHeaders(req: HttpRequest<any>): Promise<HttpHeaders> {
		let headers = new HttpHeaders().set('Accept', 'application/json');

		let portaleId = req.headers.get('portale');
		if (!portaleId) {
			const portale = this.getLocalStorageObjectJson('portale');
			if (portale) {
				portaleId = portale.pt_id.toString();
			}
		}
		if (portaleId) {
			headers = headers.set('portale', portaleId);
		}

		const currentUser = this.getLocalStorageObjectJson('currentUser');
		if (!currentUser) {
			return headers;
		}

		try {
			const token = await this.checkTokenExpiration(currentUser.token);
			if (token !== 'refreshtoken_usernotfound') {
				headers = headers.set('Authorization', `Bearer ${token}`);
			}
		} catch {
			// Return headers without auth on error
		}
		return headers;
	}

	private getLocalStorageObjectJson(key: string) {
		const val = JSON.parse(localStorage.getItem(key) || '{}');
		return Object.keys(val).length === 0 ? null : val;
	}

	private checkTokenExpiration(token: string): Promise<string> {
		return new Promise((resolve) => {
			if (this.wait_until_refreshtoken_done) {
				this.waitForTokenToBeRefreshed().then(() => {
					resolve(this.getLocalStorageObjectJson('currentUser')?.token || token);
				});
				return;
			}

			const payload = JSON.parse(atob(token.substring(token.indexOf('.') + 1, token.lastIndexOf('.'))));
			const tokenWillExpire = new Date(payload.exp * 1000);
			const timeDiff = tokenWillExpire.getTime() - new Date().getTime();

			if (timeDiff < 480000 && (timeDiff > 0 || this.getLocalStorageObjectJson('currentUser')?.usr_id === 0)) {
				this.refreshToken(token).then((newWebToken: any) => {
					this.setToken(newWebToken.token);
					this.setRefreshToken(newWebToken.refresh_token);
					resolve(newWebToken.token);
				});
			} else {
				resolve(token);
			}
		});
	}

	private refreshToken(token: string): Promise<any> {
		this.wait_until_refreshtoken_done = true;
		const headers = this.createAuthorizationHeaderTempToken(token);

		return new Promise((resolve, reject) => {
			this.http.post(`${this.configService.serviceUrl_Login}login/${this.getLocalStorageObjectJson('portale').pt_id}`, this.getLocalStorageObjectJson('currentUser'), { headers, observe: 'response' }).subscribe({
				next: (response: any) => {
					this.wait_until_refreshtoken_done = false;
					resolve(response.body);
				},
				error: (error: any) => {
					this.wait_until_refreshtoken_done = false;
					reject(error);
				},
			});
		});
	}

	private createAuthorizationHeaderTempToken(token: string): HttpHeaders {
		return new HttpHeaders().set('Authorization', `Bearer ${token}`);
	}

	private setToken(token: string) {
		const currentUser = this.getLocalStorageObjectJson('currentUser');
		if (currentUser) {
			localStorage.setItem('currentUser', JSON.stringify({ ...currentUser, token }));
		}
	}

	private setRefreshToken(refreshToken: string) {
		const currentUser = this.getLocalStorageObjectJson('currentUser');
		if (currentUser) {
			localStorage.setItem('currentUser', JSON.stringify({ ...currentUser, refresh_token: refreshToken }));
		}
	}

	private waitForTokenToBeRefreshed(): Promise<void> {
		return new Promise((resolve) => {
			const check = () => {
				if (!this.wait_until_refreshtoken_done) {
					resolve();
				} else {
					setTimeout(check, 100);
				}
			};
			check();
		});
	}
}
