// src/core/security/CapabilityRegistry.ts

import type { ICapabilityToken } from './ICapabilityToken';
import type { ICapabilityRegistry } from './ICapabilityRegistry';

/**
 * In-memory implementation of capability registry.
 *
 * Mark Miller (Capability Security, E Language):
 *   "The registry is the authority. If a capability isn't registered,
 *    it doesn't exist. Revocation removes it from the registry."
 *
 * This is a simple Map-based implementation suitable for single-runtime
 * scenarios. For distributed systems, a shared store (Redis, etc.) would
 * be needed.
 *
 * See: Vision/27-SECURITY-FEDERATION-DRAFT.md
 */
export class CapabilityRegistry implements ICapabilityRegistry {
  /** Registered tokens indexed by ID */
  private readonly _tokens = new Map<string, ICapabilityToken>();

  /** Revoked token IDs (kept for audit trail) */
  private readonly _revoked = new Set<string>();

  /** Index of tokens by principal (for getForPrincipal) */
  private readonly _byPrincipal = new Map<string, Set<string>>();

  /**
   * Get a capability token by its ID.
   *
   * Returns undefined if:
   * - Token doesn't exist
   * - Token has been revoked
   * - Token has expired
   */
  get(id: string): ICapabilityToken | undefined {
    if (this._revoked.has(id)) {
      return undefined;
    }

    const token = this._tokens.get(id);
    if (!token) {
      return undefined;
    }

    // Check expiration
    if (token.expiresAt && token.expiresAt < Date.now()) {
      // Auto-revoke expired tokens
      this.revoke(id);
      return undefined;
    }

    return token;
  }

  /**
   * Register a capability token.
   *
   * Joe Armstrong (Erlang, "Programming Erlang"):
   *   "Registration is idempotent. Register the same thing twice,
   *    you still have one thing."
   */
  register(token: ICapabilityToken): void {
    // Don't allow re-registering revoked tokens
    if (this._revoked.has(token.id)) {
      // eslint-disable-next-line no-console -- capability misuse warnings stay in kernel security layer
      console.warn(
        `[CapabilityRegistry] Attempted to register revoked token '${token.id}'. Ignored.`
      );
      return;
    }

    this._tokens.set(token.id, token);

    // Index by subject (the principal this capability grants access to)
    if (!this._byPrincipal.has(token.subject)) {
      this._byPrincipal.set(token.subject, new Set());
    }
    this._byPrincipal.get(token.subject)!.add(token.id);
  }

  /**
   * Revoke a capability token.
   *
   * Mark Miller: "Revocation is immediate and permanent within this registry.
   * Distributed revocation propagation is a separate concern."
   */
  revoke(id: string): boolean {
    const token = this._tokens.get(id);
    if (!token) {
      return false;
    }

    // Remove from main registry
    this._tokens.delete(id);

    // Add to revoked set (for audit and preventing re-registration)
    this._revoked.add(id);

    // Remove from principal index
    const principalTokens = this._byPrincipal.get(token.subject);
    if (principalTokens) {
      principalTokens.delete(id);
      if (principalTokens.size === 0) {
        this._byPrincipal.delete(token.subject);
      }
    }

    return true;
  }

  /**
   * Check if a capability token is valid.
   */
  isValid(id: string): boolean {
    return this.get(id) !== undefined;
  }

  /**
   * Get all capability tokens for a principal (subject).
   */
  getForPrincipal(principal: string): ICapabilityToken[] {
    const tokenIds = this._byPrincipal.get(principal);
    if (!tokenIds) {
      return [];
    }

    const result: ICapabilityToken[] = [];
    for (const id of tokenIds) {
      const token = this.get(id); // Uses get() to check revocation/expiration
      if (token) {
        result.push(token);
      }
    }

    return result;
  }

  /**
   * Clear all tokens (for testing).
   * @internal
   */
  _clear(): void {
    this._tokens.clear();
    this._revoked.clear();
    this._byPrincipal.clear();
  }

  /**
   * Get count of registered tokens (for diagnostics).
   */
  get size(): number {
    return this._tokens.size;
  }

  /**
   * Get count of revoked tokens (for diagnostics).
   */
  get revokedCount(): number {
    return this._revoked.size;
  }
}
