import { Provider } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { firstValueFrom, timeout, TimeoutError } from 'rxjs';
import { ApiClient, ProblemDetails } from './api-client';
import { environment } from '../../../environments/environment';

/** Single place every API call passes through: the seam for cross-cutting concerns. */
const REQUEST_TIMEOUT_MS = 10_000;

/**
 * Adapts Angular's HttpClient to the fetch-shaped interface the NSwag-generated
 * client expects, so every API call flows through HttpClient interceptors (the
 * `?scenario=` toggle) and the cross-cutting concerns below. The generated client
 * is the only place HTTP shapes are known; this is the only place it meets
 * Angular's HTTP stack — i.e. the one seam to add:
 *   - timeout (done — REQUEST_TIMEOUT_MS),
 *   - correlation id (done — X-Correlation-Id, echoed in backend logs),
 *   - idempotency key for writes (done — Idempotency-Key; a real retry would thread
 *     a STABLE key per logical submit so re-sends dedupe; here it's per-attempt),
 *   - auth: attach `Authorization: Bearer …` here (one line) when real DigiD lands,
 *   - retry/backoff: wrap the pipe with rxjs `retry({ count, delay })` here.
 */
function httpClientFetch(http: HttpClient) {
  return {
    async fetch(url: RequestInfo, init?: RequestInit): Promise<Response> {
      const method = (init?.method ?? 'GET').toUpperCase();
      const headers: Record<string, string> = {
        ...((init?.headers ?? {}) as Record<string, string>),
        'X-Correlation-Id': crypto.randomUUID(),
      };
      if (method !== 'GET') headers['Idempotency-Key'] = crypto.randomUUID();
      try {
        const res = await firstValueFrom(
          http
            .request(method, url as string, {
              body: init?.body as string | undefined,
              headers,
              observe: 'response',
              responseType: 'text',
            })
            .pipe(timeout(REQUEST_TIMEOUT_MS)),
        );
        // 204/205/304 are null-body statuses — new Response(body, …) throws for any non-null body.
        const nullBody = res.status === 204 || res.status === 205 || res.status === 304;
        return new Response(nullBody ? null : (res.body ?? ''), { status: res.status || 200 });
      } catch (e) {
        if (e instanceof TimeoutError) return new Response('', { status: 504 });
        const err = e as HttpErrorResponse;
        const body = typeof err.error === 'string' ? err.error : JSON.stringify(err.error ?? {});
        // ponytail: clamp to a Response-constructible status (an aborted/interceptor
        // request reports status 0, which `new Response` rejects).
        const status = err.status >= 200 && err.status <= 599 ? err.status : 500;
        return new Response(body, { status });
      }
    },
  };
}

/** Provide a root ApiClient that talks through HttpClient. Base URL comes from the
 *  environment (relative '' in dev → proxy; configurable per deployment). */
export function provideApiClient(): Provider {
  return {
    provide: ApiClient,
    useFactory: (http: HttpClient) => new ApiClient(environment.apiBaseUrl, httpClientFetch(http)),
    deps: [HttpClient],
  };
}

export type { ProblemDetails };
