import {
  BadRequestException,
  Body,
  Controller,
  Headers,
  HttpCode,
  HttpStatus,
  Logger,
  Post,
  UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Throttle } from '@nestjs/throttler';
import { createHash, timingSafeEqual } from 'crypto';
import { ServiceCredentialValidatorService } from '../common/services/service-credential-validator.service';

interface EvictBody {
  prefix?: string;
}

/**
 * Internal-only endpoints (M6). `POST /internal/service-credentials/evict` lets Laravel push a
 * best-effort cache eviction on revoke/rotate so the reached BFF replica drops the prefix from the
 * in-memory validator cache instantly. Guarded by the shared SERVICE_CREDENTIALS_INTERNAL_SECRET
 * (the same secret the /validate introspection call uses). NO Redis is involved.
 *
 * This is an accelerator only: even with no push, the validator's positive-cache TTL
 * (SERVICE_CRED_CACHE_TTL_MS, ~30s) bounds revocation staleness on every replica.
 */
@Controller('internal/service-credentials')
export class InternalController {
  private readonly logger = new Logger(InternalController.name);

  constructor(
    private readonly validator: ServiceCredentialValidatorService,
    private readonly config: ConfigService,
  ) {}

  @Post('evict')
  @HttpCode(HttpStatus.OK)
  @Throttle({ default: { ttl: 60_000, limit: 30 } })
  evict(
    @Headers('x-internal-auth') auth: string,
    @Body() body: EvictBody,
  ): { evicted: number } {
    this.assertInternalAuth(auth);

    const prefix = (body?.prefix ?? '').trim();
    if (!prefix) {
      throw new BadRequestException('prefix is required');
    }

    const evicted = this.validator.evict(prefix);
    this.logger.log(`internal.evict prefix=${prefix} evicted=${evicted}`);
    return { evicted };
  }

  /** Constant-time check of the shared internal secret (mirrors the introspection guard idiom). */
  private assertInternalAuth(presented: string | undefined): void {
    const expected =
      this.config.get<string>('SERVICE_CREDENTIALS_INTERNAL_SECRET') ?? '';
    if (
      !presented ||
      !expected ||
      !InternalController.timingSafeEqual(presented, expected)
    ) {
      throw new UnauthorizedException('Invalid internal auth');
    }
  }

  private static timingSafeEqual(a: string, b: string): boolean {
    const ha = createHash('sha256').update(a).digest();
    const hb = createHash('sha256').update(b).digest();
    return timingSafeEqual(ha, hb);
  }
}
