import { Injectable, inject } from '@angular/core';
import {
  HttpBackend,
  HttpClient,
  HttpErrorResponse,
  HttpEvent,
  HttpHandler,
  HttpInterceptor,
  HttpRequest,
} from '@angular/common/http';
import { Router } from '@angular/router';
import { Observable, catchError, firstValueFrom, from, switchMap, throwError } from 'rxjs';
import { AuthService } from '../services/auth.service';
import { TokenResponse } from '../models/auth.models';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  private isRefreshing = false;
  private queue: Array<(token: string | null) => void> = [];
  private readonly refreshClient: HttpClient;

  private readonly auth = inject(AuthService);
  private readonly router = inject(Router);

  constructor(httpBackend: HttpBackend) {
    this.refreshClient = new HttpClient(httpBackend);
  }

  intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    const token = this.auth.getAccessToken();
    const authReq = token
      ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })
      : req;

    return next.handle(authReq).pipe(
      catchError((error: HttpErrorResponse) => {
        const original = authReq;
        const isAuthEndpoint =
          original.url.includes('/auth/login') ||
          original.url.includes('/auth/register') ||
          original.url.includes('/auth/refresh');

        if (error.status !== 401 || isAuthEndpoint || original.headers.has('X-Retry')) {
          return throwError(() => error);
        }

        if (this.isRefreshing) {
          return new Observable<HttpEvent<unknown>>((subscriber) => {
            this.queue.push((newToken) => {
              if (!newToken) {
                subscriber.error(error);
                return;
              }
              next
                .handle(
                  original.clone({
                    setHeaders: {
                      Authorization: `Bearer ${newToken}`,
                      'X-Retry': 'true',
                    },
                  }),
                )
                .subscribe(subscriber);
            });
          });
        }

        this.isRefreshing = true;
        return from(this.performRefresh()).pipe(
          switchMap((newToken) => {
            this.isRefreshing = false;
            this.flushQueue(newToken);
            if (!newToken) {
              this.redirectToLogin();
              return throwError(() => error);
            }
            return next.handle(
              original.clone({
                setHeaders: {
                  Authorization: `Bearer ${newToken}`,
                  'X-Retry': 'true',
                },
              }),
            );
          }),
        );
      }),
    );
  }

  private async performRefresh(): Promise<string | null> {
    const refreshToken = this.auth.getRefreshToken();
    if (!refreshToken) return null;
    try {
      const tokens = await firstValueFrom(
        this.refreshClient.post<TokenResponse>('/api/auth/refresh', { refreshToken }),
      );
      if (!tokens) return null;
      this.auth.setTokens(tokens);
      return tokens.accessToken;
    } catch {
      return null;
    }
  }

  private flushQueue(token: string | null): void {
    this.queue.forEach((resolve) => resolve(token));
    this.queue = [];
  }

  private redirectToLogin(): void {
    this.auth.clearAuth();
    const path = this.router.url.split('?')[0];
    if (path !== '/login' && path !== '/register') {
      void this.router.navigateByUrl('/login');
    }
  }
}
