import { inject } from '@angular/core';
import {
  HttpRequest,
  HttpHandlerFn,
  HttpEvent,
  HttpErrorResponse,
  HttpInterceptorFn
} from '@angular/common/http';
import { BehaviorSubject, catchError, filter, Observable, switchMap, take, throwError } from 'rxjs';
import { AuthService } from '../auth/auth.service';

let refreshInProgress = false;
const refreshSubject = new BehaviorSubject<string | null>(null);

export const AuthInterceptor: HttpInterceptorFn = (
  req: HttpRequest<unknown>,
  next: HttpHandlerFn
): Observable<HttpEvent<unknown>> => {

  const authSvc: AuthService = inject(AuthService);
  let headers = req.headers;
  // add API key
  const apiKey = authSvc.getApiKey();
  if (apiKey) {
    headers = headers.set('XApiKey', apiKey);
  }

  // add content type if needed
  if (!(req.body instanceof FormData) &&
    (req.method === 'POST' || req.method === 'PUT') &&
    !headers.has('Content-Type')) {

    headers = headers.set('Content-Type', 'application/json');
  }

  // add access token
  const token = authSvc.currentToken;
  if (token) {
    headers = headers.set('Authorization', `Bearer ${token}`);
  }

  // final cloned request
  const clonedRequest = req.clone({
    headers,
    withCredentials: true
  });

  return next(clonedRequest).pipe(
    catchError((err: HttpErrorResponse) => {

      if (err.status !== 401) {
        return throwError(() => err);
      }

      // Don't attempt token refresh for auth endpoints
      const isAuthUrl = req.url.includes('/auth/');
      if (isAuthUrl) {
        return throwError(() => err);
      }

      // if refresh is already in progress
      if (refreshInProgress) {
        return refreshSubject.pipe(
          filter(t => t !== null),
          take(1),
          switchMap(newToken => {

            const retryReq = clonedRequest.clone({
              headers: clonedRequest.headers.set('Authorization', `Bearer ${newToken}`),
              withCredentials: true
            });

            return next(retryReq);
          })
        );
      }

      // start refresh
      refreshInProgress = true;
      refreshSubject.next(null);

      const refresh$ = authSvc.refreshToken();

      // refreshToken() must return an Observable. Otherwise handle gracefully
      if (!refresh$) {
        refreshInProgress = false;
        authSvc.logout();
        return throwError(() => err);
      }

      return refresh$.pipe(

        switchMap(res => {
          refreshInProgress = false;

          const newToken = res?.accessToken;

          if (!newToken) {
            authSvc.logout();
            return throwError(() => err);
          }

          authSvc.safeSetToken(newToken);
          refreshSubject.next(newToken);

          const retryReq = clonedRequest.clone({
            headers: clonedRequest.headers.set('Authorization', `Bearer ${newToken}`),
            withCredentials: true
          });

          return next(retryReq);
        }),

        catchError(refreshErr => {
          refreshInProgress = false;
          refreshSubject.next(null);

          authSvc.logout();
          return throwError(() => refreshErr);
        })
      );
    })
  );
};