import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Injectable, computed, effect, inject, signal } from '@angular/core';
import { firstValueFrom } from 'rxjs';
import { AUTH_BFF_BASE_URL, AuthService } from 'feature-auth';

/**
 * Curated capabilities view served by the BFF at
 * `GET /api/me/capabilities` (ADR-0009 amendment in this PR).
 * Mirrors the BFF's `CapabilitiesView` interface 1:1.
 */
export interface Capabilities {
  readonly canAccessAdmin: boolean;
}

/** Anonymous default — used while auth state is loading or anonymous. */
const ANONYMOUS_CAPABILITIES: Capabilities = { canAccessAdmin: false };

/**
 * `CapabilitiesService` — fetches `/api/me/capabilities` whenever the
 * `AuthService` flips into `authenticated`, exposes the resulting
 * booleans as signals for the header / sidebar to read.
 *
 * Per ADR-0009 §"curated public view" the user-portal `/me`
 * deliberately omits the raw `roles` claim; the SPA derives binary
 * UX flags from this companion endpoint instead. Today the only
 * flag is `canAccessAdmin` — drives the "Open Portal Admin" entry
 * in the user menu and the "Role: Administrator" label in the
 * sidebar widget.
 *
 * Anonymous traffic doesn't trigger a fetch (the BFF would 401);
 * the signal stays at the all-false default. The service re-fetches
 * automatically when sign-in completes thanks to the `effect` on
 * `auth.currentUser`.
 */
@Injectable({ providedIn: 'root' })
export class CapabilitiesService {
  private readonly http = inject(HttpClient);
  private readonly auth = inject(AuthService);
  private readonly bffBaseUrl = inject(AUTH_BFF_BASE_URL);

  private readonly _capabilities = signal<Capabilities>(ANONYMOUS_CAPABILITIES);

  readonly capabilities = this._capabilities.asReadonly();
  readonly canAccessAdmin = computed(() => this._capabilities().canAccessAdmin);

  constructor() {
    // Reactive plumbing: whenever the authenticated user signal
    // changes (initial bootstrap, post-login redirect return, sign-
    // out), re-resolve the capabilities. The effect resets the
    // payload to the anonymous default before fetching so a stale
    // "yes admin" from a previous user session doesn't leak into
    // the new one.
    effect(() => {
      const user = this.auth.currentUser();
      if (user) {
        void this.refresh();
      } else {
        this._capabilities.set(ANONYMOUS_CAPABILITIES);
      }
    });
  }

  async refresh(): Promise<void> {
    try {
      const caps = await firstValueFrom(
        this.http.get<Capabilities>(`${this.bffBaseUrl}/me/capabilities`),
      );
      this._capabilities.set(caps);
    } catch (err) {
      // 401 here means the session expired between the auth state
      // bootstrap and our fetch — collapse to anonymous. Any other
      // error is rare enough (network blip, BFF 500) that conservative
      // "no extra UI" is the right default; the `AuthService`'s own
      // 401 interceptor will re-bootstrap the state on the next call.
      if (err instanceof HttpErrorResponse) {
        this._capabilities.set(ANONYMOUS_CAPABILITIES);
      }
    }
  }
}
