import { HttpErrorResponse, HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
import { throwError, timer } from 'rxjs';
import { catchError, mergeMap, retry } from 'rxjs/operators';
import { toApiError } from '../error-handling/api-error.mapper';
import { ApiError } from '../error-handling/models/api-error.model';
import { RateLimitNotifier } from '../error-handling/rate-limit-notifier.service';

/**
 * Normalizes every failed response into an ApiError, and honours 429
 * Retry-After instead of failing straight through.
 *
 * Contract: BACKEND-API-REFERENCE.md §5 (envelope), TRACK-S §5 (rate limits).
 *
 * Before this existed, every caller branched on raw HttpErrorResponse.status
 * and nothing anywhere handled 429 — a rate-limited backend surfaced as a
 * generic failure with no retry and no user-visible explanation.
 */

/** Retries are bounded: past this, the caller sees the error. */
const MAX_RATE_LIMIT_RETRIES = 2;

/** Never sleep longer than this on a server-suggested delay. */
const MAX_RETRY_DELAY_SECONDS = 30;

export const apiErrorInterceptor: HttpInterceptorFn = (req, next) => {
  const notifier = inject(RateLimitNotifier);

  return next(req).pipe(
    retry({
      count: MAX_RATE_LIMIT_RETRIES,
      delay: (error: unknown, retryCount: number) => {
        if (!(error instanceof HttpErrorResponse) || error.status !== 429) {
          return throwError(() => error);
        }

        // Only retry requests that are safe to repeat. Replaying a POST after
        // a 429 can double-submit; that decision belongs to the caller, which
        // knows whether it holds an idempotency key.
        if (!isIdempotent(req.method)) {
          return throwError(() => error);
        }

        const apiError = toApiError(error);
        const waitSeconds = Math.min(
          apiError.retryAfterSeconds ?? retryCount * 5,
          MAX_RETRY_DELAY_SECONDS,
        );

        notifier.report(waitSeconds, req.url);
        return timer(waitSeconds * 1000);
      },
    }),
    catchError((error: unknown) => {
      if (!(error instanceof HttpErrorResponse)) {
        return throwError(() => error);
      }

      const apiError: ApiError = toApiError(error);

      if (apiError.status === 429) {
        notifier.report(apiError.retryAfterSeconds ?? 0, req.url);
      }

      return throwError(() => apiError);
    }),
  );
};

function isIdempotent(method: string): boolean {
  const m = method.toUpperCase();
  return m === 'GET' || m === 'HEAD' || m === 'OPTIONS';
}

/** Exported for tests only. */
export const __testing = { MAX_RATE_LIMIT_RETRIES, MAX_RETRY_DELAY_SECONDS, isIdempotent };
