import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { AUTH_BFF_BASE_URL } from 'feature-auth';
import type { Observable } from 'rxjs';

/**
 * One row of the BFF's `GET /api/admin/audit` response. Mirrors
 * `AdminAuditEventDto` on the BFF side — ISO-string timestamp,
 * already-parsed JSON payload, hashed actor id.
 */
export interface AdminAuditEvent {
  readonly id: string;
  readonly createdAt: string;
  readonly eventType: string;
  readonly audience: string;
  readonly actorIdHash: string | null;
  readonly traceId: string | null;
  readonly subject: string | null;
  readonly outcome: string;
  readonly payload: Record<string, unknown> | null;
}

/** Paginated response shape — mirrors `AdminAuditPage` on the BFF. */
export interface AdminAuditPage {
  readonly items: readonly AdminAuditEvent[];
  readonly total: number;
  readonly limit: number;
  readonly offset: number;
}

/**
 * Filter+pagination shape sent as query params. All fields are
 * optional; missing values fall back to the BFF defaults (limit=50,
 * offset=0, no filter).
 */
export interface AdminAuditQuery {
  // Both optional AND explicitly nullable: `exactOptionalPropertyTypes`
  // rejects assigning a `T | undefined` to a `?: T`, but the
  // AuditPage `buildFilters()` builds objects with `undefined`
  // placeholders for missing user input. Allowing both `?:` and
  // `| undefined` lets callers omit the key entirely OR set it to
  // undefined, both meaning "no filter".
  readonly eventType?: string | undefined;
  readonly actorIdHash?: string | undefined;
  readonly audience?: 'workforce' | 'customer' | undefined;
  readonly outcome?: 'success' | 'failure' | 'denied' | undefined;
  readonly subjectPrefix?: string | undefined;
  readonly createdAtFrom?: string | undefined;
  readonly createdAtTo?: string | undefined;
  readonly limit?: number | undefined;
  readonly offset?: number | undefined;
}

/**
 * Thin HttpClient wrapper. Lives in `providedIn: 'root'` because the
 * audit page is the single v1 consumer and per-page DI overhead is
 * not worth it. The wrapper exists at all (vs HttpClient directly in
 * the component) so the audit page's logic stays signal-only and the
 * spec can mock the service rather than threading
 * `HttpTestingController` through every assertion.
 */
@Injectable({ providedIn: 'root' })
export class AuditEventsService {
  private readonly http = inject(HttpClient);
  private readonly bffBaseUrl = inject(AUTH_BFF_BASE_URL);

  query(filters: AdminAuditQuery): Observable<AdminAuditPage> {
    let params = new HttpParams();
    for (const [key, value] of Object.entries(filters)) {
      // Drop empty strings — Nest's ValidationPipe treats `?foo=`
      // as `foo === ''` which trips the IsISO8601 / IsString
      // validators and surfaces as a 400. The SPA passes empty
      // inputs when the user hasn't filled a filter; this strips
      // them out so the BFF only sees populated fields.
      if (value === undefined || value === null || value === '') {
        continue;
      }
      params = params.set(key, String(value));
    }
    return this.http.get<AdminAuditPage>(`${this.bffBaseUrl}/admin/audit`, { params });
  }
}
