import { Injectable } from '@nestjs/common';

/**
 * Server-side verifier store for the PKCE Authorization Code flow.
 *
 * Responsibility: correlate the `code_verifier` (generated at `/auth/login`) with the
 * `state` parameter that round-trips through the browser, so the BFF can include the
 * verifier in the `/token` exchange WITHOUT ever sending it to the browser.
 *
 * Design decisions (from design.md D1):
 *   - In-memory `Map` + short TTL: acceptable for a single-instance satellite. For
 *     multi-replica deployments, replace this service with a Redis-backed implementation
 *     — the injectable interface is intentionally minimal so the swap is trivial.
 *   - Single-use: `take` deletes the entry so replaying the same `state` always returns null.
 *   - Short TTL (~5 min default): a stale/abandoned login attempt cannot linger indefinitely.
 *
 * DO NOT store the verifier on the client side (cookie, session) — that defeats PKCE for a
 * confidential BFF.
 */
@Injectable()
export class PkceVerifierStoreService {
  /** TTL in milliseconds. Defaults to 5 minutes. */
  private readonly ttlMs: number;

  private readonly entries = new Map<string, { verifier: string; expiresAt: number }>();

  /**
   * @param ttlMs - Optional override for the TTL, primarily for unit testing.
   *                Production code should rely on the default (5 min).
   */
  constructor(ttlMs: number = 5 * 60 * 1_000) {
    this.ttlMs = ttlMs;
  }

  /**
   * Stores `verifier` under `state` for at most `ttlMs` milliseconds.
   * Overwrites any existing entry for the same `state`.
   */
  put(state: string, verifier: string): void {
    this.entries.set(state, {
      verifier,
      expiresAt: Date.now() + this.ttlMs,
    });
  }

  /**
   * Retrieves and DELETES the verifier associated with `state`.
   *
   * Returns the verifier string on the first call, then `null` on every subsequent
   * call (single-use guarantee). Also returns `null` when:
   *   - `state` was never stored.
   *   - The entry has expired (TTL elapsed).
   */
  take(state: string): string | null {
    const entry = this.entries.get(state);

    if (!entry) return null;

    // Expired — clean up and treat as absent.
    if (Date.now() > entry.expiresAt) {
      this.entries.delete(state);
      return null;
    }

    // Single-use: delete before returning.
    this.entries.delete(state);
    return entry.verifier;
  }
}
