import { Inject, Injectable } from '@nestjs/common';
import { Logger } from 'nestjs-pino';
import { REDIS_CLIENT, type Redis } from '../redis/redis.token';

/**
 * Secondary index `user_sessions:{userId}` → Redis set of session
 * ids the user currently has open (per ADR-0010 §"Revocation").
 *
 * Maintained on the lifecycle events:
 *   - session created in `/auth/callback`        → `SADD`
 *   - session destroyed in `/auth/logout`        → `SREM`
 *   - session destroyed by the absolute-timeout  → `SREM`
 *
 * NOT actively cleaned on idle-TTL expiry: when the Redis session
 * key disappears on its own, the corresponding entry in the user
 * set becomes a "phantom" id pointing at nothing. ADR-0010 treats
 * this as best-effort — orphans are filtered out by `list()` /
 * future admin "logout everywhere" routes.
 *
 * Today the only consumer of `add` / `remove` is the auth surface.
 * `list` and the matching admin endpoint land in a follow-up PR
 * (admin module / `@RequireAdmin` guard).
 */
@Injectable()
export class UserSessionIndexService {
  private static readonly KEY_PREFIX = 'user_sessions:';

  constructor(
    @Inject(REDIS_CLIENT) private readonly redis: Redis,
    private readonly logger: Logger,
  ) {}

  async add(userId: string, sessionId: string): Promise<void> {
    try {
      await this.redis.sadd(this.keyFor(userId), sessionId);
    } catch (err) {
      // Best-effort by design: a failed SADD leaves the session
      // unreachable from "logout everywhere" but does not break
      // sign-in itself. Log so ops can spot a degraded Redis.
      this.logger.warn(
        {
          event: 'user_session_index.add_failed',
          userId,
          message: err instanceof Error ? err.message : String(err),
        },
        'UserSessionIndex',
      );
    }
  }

  async remove(userId: string, sessionId: string): Promise<void> {
    try {
      await this.redis.srem(this.keyFor(userId), sessionId);
    } catch (err) {
      this.logger.warn(
        {
          event: 'user_session_index.remove_failed',
          userId,
          message: err instanceof Error ? err.message : String(err),
        },
        'UserSessionIndex',
      );
    }
  }

  /**
   * Returns the raw set of session ids for a user. Includes
   * orphans (entries whose `session:<id>` Redis key has already
   * expired); the caller is expected to filter or rely on idle
   * cleanup. v1 has no in-product consumer — exposed for tests
   * today and the upcoming admin module.
   */
  async list(userId: string): Promise<string[]> {
    return this.redis.smembers(this.keyFor(userId));
  }

  private keyFor(userId: string): string {
    return `${UserSessionIndexService.KEY_PREFIX}${userId}`;
  }
}
