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

/**
 * Intercepteur HTTP :
 * 1. Ajoute l'en-tête `Authorization: Bearer <access_token>` aux requêtes /api
 *    (sauf aux endpoints d'authentification eux-mêmes).
 * 2. Sur une réponse 401, tente UN refresh puis rejoue la requête. Les requêtes
 *    concurrentes pendant le refresh sont mises en file et rejouées une fois le
 *    nouveau token disponible.
 */

// État partagé du rafraîchissement (au niveau module : un seul refresh à la fois).
let refreshEnCours = false;
const tokenRafraichi$ = new BehaviorSubject<string | null>(null);

/** Endpoints qui ne doivent pas porter de Bearer ni déclencher de refresh. */
const ENDPOINTS_AUTH = ['/api/auth/login', '/api/auth/register', '/api/auth/refresh'];

function estEndpointAuth(url: string): boolean {
  return ENDPOINTS_AUTH.some((e) => url.includes(e));
}

function ajouterToken(
  req: HttpRequest<unknown>,
  token: string,
): HttpRequest<unknown> {
  return req.clone({
    setHeaders: { Authorization: `Bearer ${token}` },
  });
}

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const auth = inject(AuthService);

  // Les endpoints d'auth passent tels quels.
  if (estEndpointAuth(req.url)) {
    return next(req);
  }

  const accessToken = auth.getAccessToken();
  const requete = accessToken ? ajouterToken(req, accessToken) : req;

  return next(requete).pipe(
    catchError((erreur: unknown) => {
      if (
        erreur instanceof HttpErrorResponse &&
        erreur.status === 401 &&
        auth.aUneSessionPotentielle()
      ) {
        return gererErreur401(req, next, auth);
      }
      return throwError(() => erreur);
    }),
  );
};

/**
 * Gère un 401 : déclenche (ou attend) un refresh, puis rejoue la requête.
 */
function gererErreur401(
  req: HttpRequest<unknown>,
  next: HttpHandlerFn,
  auth: AuthService,
): Observable<HttpEvent<unknown>> {
  if (refreshEnCours) {
    // Un refresh est déjà en cours : on attend le nouveau token puis on rejoue.
    return tokenRafraichi$.pipe(
      filter((token): token is string => token !== null),
      take(1),
      switchMap((token) => next(ajouterToken(req, token))),
    );
  }

  refreshEnCours = true;
  tokenRafraichi$.next(null);

  return auth.refresh().pipe(
    switchMap((res) => {
      refreshEnCours = false;
      tokenRafraichi$.next(res.access_token);
      return next(ajouterToken(req, res.access_token));
    }),
    catchError((erreur: unknown) => {
      // Le refresh a échoué : session invalide, on purge et on propage l'erreur.
      refreshEnCours = false;
      auth.viderSession();
      return throwError(() => erreur);
    }),
  );
}
