import { HttpHandler, HttpRequest, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, Subject, throwError } from 'rxjs';
import { catchError, exhaustMap, finalize, take } from 'rxjs/operators';
import * as moment from 'moment';
import { MessageService } from 'primeng/api';
import { LoaderService } from './loader.service';
import { API } from '../services/api.service';
import { securityFunctions } from './security';
import { UserLog } from '../models/common-model/user-logout.model';
import { UserLoginData } from '../models/common-model/userModel';
import { CommonFunctionService } from './common-function.service';

@Injectable({
  providedIn: 'root'
})
export class InterceptLoader {
  private readonly requests: HttpRequest<any>[] = [];
  LoginUserData: UserLoginData
  constructor(
    private readonly loaderService: LoaderService,
    private readonly apiServ: API,
    public MsgService: MessageService,
    private readonly commonService: CommonFunctionService) {
  }

  removeRequest(req: HttpRequest<any>) {
    const i = this.requests.indexOf(req);
    if (i >= 0) {
      this.requests.splice(i, 1);
    }
    if (!req.url.includes('RetryInvoice') && !req.url.includes('invoice/GetCustomer') && !req.url.includes('UserMachine/CheckUserVerified')) {
      this.loaderService.isLoading.next(this.requests.length > 0);
    }
  }
  private readonly refreshToken$ = new Subject<void>();
  private refreshInProgress = false;
  intercept(req: HttpRequest<any>, next: HttpHandler) {
    const modifiedRequest = this.headerSetup(req);
    const adjustedTime = this.apiServ.getAdjustedLocalTime(); // Get adjusted local time
    const currentISOString = moment.utc(adjustedTime).format('YYYY-MM-DD HH:mm:ss');
    const time1 = moment.utc(sessionStorage.getItem('JwtTokenExpiry')).subtract(3, 'seconds').format('YYYY-MM-DD HH:mm:ss');
    const isTime1AfterTime2 = this.isTime1AfterTime2(time1, currentISOString);
    const isRefreshTokenUrl = req.url.toLowerCase().includes('/jwt/refreshtoken');
    const isLogoutLogUrl = req.url.toLowerCase().includes('/log/savelogoutlog');
    let UGT = sessionStorage.getItem('UGT');
    const isServerUTCTimeUrl = req.url.includes('/Server/GetServerUTCTime');
    if (((isTime1AfterTime2) && !isRefreshTokenUrl && !isLogoutLogUrl) || (!UGT && !isServerUTCTimeUrl)) {
      // If a token refresh is in progress, wait for it to complete before proceeding
      if (this.refreshInProgress) {
        return this.refreshToken$.pipe(
          take(1),
          exhaustMap(() => {
            const modifiedRequest = this.headerSetup(req);
            this.requests.push(modifiedRequest);
            if (!req.url.includes('RetryInvoice') && !req.url.includes('invoice/GetCustomer') && !req.url.includes('UserMachine/CheckUserVerified')) {
              this.loaderService.isLoading.next(true);
            }
            return this.ObservableReturn(modifiedRequest, next)
          })
        );
      }
      // Trigger token refresh and queue other requests
      this.refreshInProgress = true;
      this.refreshToken$.next();
      if (!UGT && !isServerUTCTimeUrl) {
        return this.GetServerTime(req, next);
      } else {
        return this.GetJWTToKen(req, next);
      }
    } else {
      this.requests.push(modifiedRequest);
      if (!req.url.includes('RetryInvoice') && !req.url.includes('invoice/GetCustomer') && !req.url.includes('UserMachine/CheckUserVerified')) {
        this.loaderService.isLoading.next(true);
      }
      return this.ObservableReturn(modifiedRequest, next);
    }
  }

  isTime1AfterTime2(time1, time2) {
    const date1 = new Date(time1); // Create a Date object for time1
    const date2 = new Date(time2); // Create a Date object for time2
    return date1 < date2 ? true : false; // Compare the two Date objects
  }

  ObservableReturn(req, next) {
    return Observable.create(observer => {
      const subscription = next.handle(req).subscribe(
        event => {
          if (event instanceof HttpResponse) {

            this.removeRequest(req);
            observer.next(event);

          }
        },
        err => {
          if (err.status === 401) {
            // Response status is 401, so you can run your function here
            const customResponse = err; // Pass the response to your function
            // updated by Abdul Razzak on 23 mar 2024
            if (err.url.toLowerCase().includes('/log/savelogoutlog')) {
              window.location.href = '/Auth/Login';
            } else {
              this.handleUnauthorizedResponse(customResponse.error); // Replace this with your actual function
            }
            this.removeRequest(req);
          } else if (err.status === 429) {
            this.MsgService.add({
              severity: "error",
              detail: err.error["metadata"]?.message ? err.error["metadata"]?.message : 'Service is currently unavailable',
            });
            this.removeRequest(req);
          } else {
            this.removeRequest(req);
            console.log('error' + err);
            observer.error(err);
          }
        },
        () => {
          this.removeRequest(req);
          observer.complete();
        });
      // remove request from queue when cancelled
      return () => {
        this.removeRequest(req);
        subscription.unsubscribe();
      };
    });
  }

  handleUnauthorizedResponse(Response) {
    if (Response.AuthResponseType == 2 || Response.AuthResponseType == 1 || Response.AuthResponseType == 4 || Response.AuthResponseType == 5 || Response.AuthResponseType == 6 || Response.AuthResponseType == 7 || Response.AuthResponseType == 8) {
      this.loginOut();
    }
    if (Response.AuthResponseType == 3) {
      this.MsgService.add({
        severity: "error",
        detail: Response["message"],
      });
    }
  }

  loginOut() {
    let loginData = sessionStorage.getItem('IP');
    let LoginUserData = JSON.parse(securityFunctions.getSessionStorage('LoginUserData'));
    try {
      let userLogModel = new UserLog();
      userLogModel.ip = loginData['iP'];
      userLogModel.ip_city = loginData['city'];
      userLogModel.ip_country_code = loginData['countryCode'];
      userLogModel.ip_lat = loginData['lat'];
      userLogModel.ip_lon = loginData['lon'];
      userLogModel.ip_proxy = loginData['proxy'];
      userLogModel.user_id = LoginUserData.user_id;
      userLogModel.user_type = LoginUserData.user_type;
      this.apiServ.AdminPost("/Log/SaveLogoutLog", userLogModel).subscribe(d => {
        securityFunctions.resetSessionStoage('lastLogin')
        securityFunctions.resetSessionStoage('LoginUserData')
        securityFunctions.resetSessionStoage('IP')
        securityFunctions.resetSessionStoage('userName')
        securityFunctions.resetSessionStoage('userType')
        securityFunctions.resetSessionStoage('JwtToken')
        securityFunctions.resetSessionStoage('RefreshToken')
        securityFunctions.resetSessionStoage('JwtTokenExpiry')
        sessionStorage.removeItem('selMenu');
        window.location.href = '/Auth/Login';
      });
    } catch (error) {
      window.location.href = '/Auth/Login';
    }
  }
  GetJWTToKen(req,next) {
    let postdata = {
      "JwtToken": sessionStorage.getItem('JwtToken'),
      "RefreshToken": sessionStorage.getItem('RefreshToken')
    };
    return this.apiServ.AdminPost('/JWT/RefreshToken', postdata).pipe(
      catchError((error) => {
        // return throwError(error);
        if (error.status === 401) {
          // Response status is 401, so you can run your function here
          const customResponse = error; // Pass the response to your function
          // updated by Abdul Razzak on 04 apr 2024
          this.handleUnauthorizedResponse(customResponse.error); // Replace this with your actual function
          this.removeRequest(req);
          return throwError(error);
        } else {
          console.log('Error:', error);
          this.removeRequest(req);
          return throwError(error);
        }
      }),
      finalize(() => {
        this.refreshInProgress = false;
        this.refreshToken$.next(); // Signal completion of token refresh
      }),
      exhaustMap((res: any) => {
        if (!res.Status) {
          this.loginOut();
          const modifiedRequest = this.headerSetup(req);
          this.requests.push(modifiedRequest);
        } else {
          sessionStorage.setItem('JwtToken', res.JwtToken);
          sessionStorage.setItem('RefreshToken', res.RefreshToken);
          sessionStorage.setItem('JwtTokenExpiry', res.JwtTokenExpiry);
          const modifiedRequest = this.headerSetup(req);
          if (!req.url.includes('RetryInvoice') && !req.url.includes('invoice/GetCustomer') && !req.url.includes('UserMachine/CheckUserVerified') && !req.url.toLowerCase().includes('/jwt/refreshtoken')) {
            this.loaderService.isLoading.next(true);
          }
          this.commonService.StartTimer();
          return this.ObservableReturn(modifiedRequest, next);
        }
      })
    );
  }

  GetServerTime(req, next) {
    return this.apiServ.GetGo('/Server/GetServerUTCTime').pipe(
      catchError((error) => {
        console.log('Error:', error);
        this.removeRequest(req);
        return throwError(error);
      }),
      finalize(() => {
        this.refreshInProgress = false;
        this.refreshToken$.next(); // Signal completion of token refresh
      }),
      exhaustMap((res) => {
        this.SetuserLocalGapTime(res)
        this.loaderService.isLoading.next(true);
        let modifiedRequest = this.headerSetup(req);
        return this.ObservableReturn(modifiedRequest, next);
      })
    );
  };
  SetuserLocalGapTime(data) {
    let gaptime = data.server_utc_time - new Date().getTime();
    sessionStorage.setItem('UGT', gaptime.toString());
  }

  headerSetup(req) {
    this.LoginUserData = securityFunctions.getSessionStorage('LoginUserData') ? JSON.parse(securityFunctions.getSessionStorage('LoginUserData')) : '';
    let domain = document.location.host
    // let domain = 'extranet.dev.futuretravelplatform.com'
    let domainName = domain.replace(/^(https?:\/\/)?/, '');
    const adjustedTime = this.apiServ.getAdjustedLocalTime(); // Get adjusted local time
    // Handle vendor id in token 
    // Don't remove '~' as there was a length undefined in the API to get vendor id
    let domain_time = domainName + '~' + adjustedTime + '~' + '' + '~' + '' + '~' + '' + '~' + (this.LoginUserData ? this.LoginUserData.extranet_vendor_id : '');
    let key = securityFunctions.rsa_public_key;
    let encrypted_string = securityFunctions.encryptRSA(key, domain_time).toString();
    if (!req.url.toLowerCase().includes('/jwt/refreshtoken')) {
      sessionStorage.setItem('LastEndpointCallTime', moment.utc(adjustedTime).format('YYYY-MM-DD HH:mm:ss'));
    }
    return req.clone({
      setHeaders: {
        'Authorization': 'Bearer ' + sessionStorage.getItem('JwtToken'),
        'token': encrypted_string
      }
    });
  }

}
