import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { JwksClient } from 'jwks-rsa';
import { PrismaService } from '../prisma/prisma.service';
import type {
  ClientRegistryEntry,
  ClientRegistryPort,
  NonceStorePort,
  PublicKeyResolverPort,
} from './context-attestation';

/** Client registry backed by Postgres (IiosClientRegistry). */
@Injectable()
export class PrismaClientRegistry implements ClientRegistryPort {
  constructor(private readonly prisma: PrismaService) {}

  async find(clientId: string): Promise<ClientRegistryEntry | null> {
    const r = await this.prisma.iiosClientRegistry.findUnique({ where: { clientId } });
    if (!r) return null;
    return {
      clientId: r.clientId,
      clientType: r.clientType,
      allowedAppIds: r.allowedAppIds,
      attestSecret: r.attestSecret ?? undefined,
      jwksUri: r.jwksUri ?? undefined,
      status: r.status === 'ACTIVE' ? 'ACTIVE' : 'DISABLED',
    };
  }
}

/**
 * Nonce ledger backed by Postgres (IiosAttestationNonce). Reserve-if-absent is atomic via the
 * unique primary key: a duplicate insert (P2002) means the nonce was already used → replay.
 */
@Injectable()
export class PrismaNonceStore implements NonceStorePort {
  constructor(private readonly prisma: PrismaService) {}

  async reserve(input: { nonce: string; clientId: string; expiresAt: Date }): Promise<boolean> {
    try {
      await this.prisma.iiosAttestationNonce.create({ data: input });
      return true;
    } catch (e) {
      if (e instanceof Prisma.PrismaClientKnownRequestError && e.code === 'P2002') return false; // already seen
      throw e;
    }
  }
}

/**
 * Resolves a client's public signing key from its JWKS (prod ES256 path). Keeps ONE JwksClient
 * per jwksUri — the client caches keys and refetches on a cache miss (so key rotation is picked up
 * without a restart). Returns null on any failure so the verifier fails closed with NO_KEY.
 */
@Injectable()
export class JwksPublicKeyResolver implements PublicKeyResolverPort {
  private readonly clients = new Map<string, JwksClient>();

  private clientFor(jwksUri: string): JwksClient {
    let c = this.clients.get(jwksUri);
    if (!c) {
      c = new JwksClient({ jwksUri, cache: true, cacheMaxEntries: 8, cacheMaxAge: 10 * 60_000, rateLimit: true, jwksRequestsPerMinute: 12 });
      this.clients.set(jwksUri, c);
    }
    return c;
  }

  async resolve(jwksUri: string, kid: string): Promise<string | null> {
    try {
      const key = await this.clientFor(jwksUri).getSigningKey(kid);
      return key.getPublicKey(); // PEM (SPKI) — works for EC (ES256) and RSA keys
    } catch {
      return null; // unknown kid / unreachable JWKS / malformed key → fail closed
    }
  }
}
