import { BadRequestException, Inject, Injectable } from '@nestjs/common';
import { APP_ENVIRONMENT } from '../../configuration/src';
import type { AppEnvironment } from '../../configuration/src';
import { OidcDiscoveryService } from './oidc-discovery.service';

export interface AuthorizationCodeExchangeRequest {
  code: string;
  codeVerifier: string;
  redirectUri: string;
}

export interface AuthorizationCodeExchangeResult {
  accessToken: string;
  expiresIn: number;
}

interface TokenEndpointResponse {
  access_token: string;
  expires_in: number;
  refresh_token?: string;
  id_token?: string;
}

/**
 * Exchanges an OIDC authorization code for tokens on behalf of the (confidential,
 * client-secret-bearing) SPA client. The secret never reaches the browser: the
 * frontend performs the Authorization Code + PKCE redirect itself, then hands the
 * resulting code + PKCE verifier to this service, which is the only place the
 * client secret is used.
 */
@Injectable()
export class TokenExchangeService {
  constructor(
    @Inject(APP_ENVIRONMENT) private readonly environment: AppEnvironment,
    private readonly discovery: OidcDiscoveryService,
  ) {}

  async exchangeAuthorizationCode(
    request: AuthorizationCodeExchangeRequest,
  ): Promise<AuthorizationCodeExchangeResult> {
    const body = new URLSearchParams({
      grant_type: 'authorization_code',
      client_id: this.environment.oidcClientId,
      client_secret: this.environment.oidcClientSecret,
      code: request.code,
      code_verifier: request.codeVerifier,
      redirect_uri: request.redirectUri,
    });

    const response = await fetch(this.discovery.getTokenEndpoint(), {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: body.toString(),
    });

    if (!response.ok) {
      throw new BadRequestException(
        'The identity provider rejected the authorization code',
      );
    }

    const tokenResponse = (await response.json()) as TokenEndpointResponse;
    return {
      accessToken: tokenResponse.access_token,
      expiresIn: tokenResponse.expires_in,
    };
  }
}
