import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { createHash, createPublicKey } from 'node:crypto';
import { readFileSync } from 'node:fs';
import { resolveAuthenticationMode } from '@aurora/modules/authentication/domain/resolve-authentication-mode';

const DEFAULT_PUBLIC_KEY_PATH = './.keys/oauth-public.key';

/**
 * RFC 7517 — JSON Web Key Set shape. The keys array typically contains exactly one entry
 * (this instance's signing key); a future rotation flow may publish two keys briefly.
 *
 * In aurora-hub mode (satellite) the handler is dormant: it skips readFileSync and
 * returns an empty key set. A satellite has no local signing key to advertise — the hub
 * publishes its own JWKS.
 */
export interface JwksDocument {
  keys: Array<{
    kty: string;
    alg: string;
    use: string;
    kid: string;
    n?: string;
    e?: string;
  }>;
}

/**
 * Builds the JWKS document advertised at `GET /.well-known/jwks.json`. Loads the local
 * RSA public key (path configurable via `OAUTH_PUBLIC_KEY_PATH`), converts it to JWK,
 * and computes a deterministic `kid` from the PEM contents.
 *
 * The document is built ONCE at module instantiation and served from memory — the key
 * material is static for a given deployment and re-reading the file per request would
 * waste I/O. To rotate the key, restart the process.
 *
 * **Dormant in non-local-provider modes**: in aurora-hub or none mode, `readFileSync` is
 * skipped and `main()` returns `{ keys: [] }`. A satellite delegates auth to the hub,
 * which publishes its own JWKS; a none-mode install has no signing key at all.
 *
 * The transport (`JwksController`) only exposes this handler's output at the HTTP path;
 * all RFC 7517 logic lives here.
 */
@Injectable()
export class OAuthGetJwksHandler {
  private readonly jwks: JwksDocument;

  constructor(configService: ConfigService) {
    const mode = resolveAuthenticationMode(configService.get<string>('OAUTH_STRATEGY'));

    if (mode !== 'local-provider') {
      // In aurora-hub or none mode: skip readFileSync. This instance is dormant —
      // a satellite has no local signing key to advertise, and a none-mode install
      // has no signing key at all. Return an empty key set.
      this.jwks = { keys: [] };
      return;
    }

    const publicKeyPath = configService.get<string>(
      'OAUTH_PUBLIC_KEY_PATH',
      DEFAULT_PUBLIC_KEY_PATH,
    );
    const publicKeyPem = readFileSync(publicKeyPath, 'utf-8');

    const keyObject = createPublicKey({ key: publicKeyPem, format: 'pem' });
    const jwk = keyObject.export({ format: 'jwk' }) as { n?: string; e?: string };

    // Deterministic kid from the PEM content. Allows clients to cache by key id and
    // detect rotation when the kid changes.
    const kid = createHash('sha256').update(publicKeyPem).digest('hex').substring(0, 16);

    this.jwks = {
      keys: [
        {
          kty: 'RSA',
          alg: 'RS256',
          use: 'sig',
          kid,
          n: jwk.n,
          e: jwk.e,
        },
      ],
    };
  }

  main(): JwksDocument {
    return this.jwks;
  }
}
