/**
 * Enhanced Microsoft Entra VerifiedID connector
 * Adds retry logic, multi-manifest support, and improved error handling
 */

import { EntraVerifiedIDClient, EntraVerifiedIDConfig, VerifiableCredentialRequest, VerifiableCredentialResponse, VerifiableCredentialStatus, VerifiedCredential } from './entra-verifiedid';

export interface RetryConfig {
  maxRetries?: number;
  initialDelayMs?: number;
  maxDelayMs?: number;
  backoffMultiplier?: number;
  retryableStatusCodes?: number[];
}

export interface MultiManifestConfig extends EntraVerifiedIDConfig {
  manifests?: Record<string, string>; // manifest name -> manifest ID mapping
}

const DEFAULT_RETRY_CONFIG: Required<RetryConfig> = {
  maxRetries: 3,
  initialDelayMs: 1000,
  maxDelayMs: 10000,
  backoffMultiplier: 2,
  retryableStatusCodes: [429, 500, 502, 503, 504],
};

/**
 * Sleep utility for retry delays
 */
function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

/**
 * Check if an error is retryable
 */
function isRetryableError(statusCode: number, retryableStatusCodes: number[]): boolean {
  return retryableStatusCodes.includes(statusCode);
}

/**
 * Enhanced Entra VerifiedID client with retry logic and multi-manifest support
 */
export class EnhancedEntraVerifiedIDClient extends EntraVerifiedIDClient {
  private retryConfig: Required<RetryConfig>;
  private manifests: Record<string, string>;

  constructor(config: MultiManifestConfig, retryConfig?: RetryConfig) {
    super(config);
    this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...retryConfig };
    this.manifests = config.manifests || {};
    // Add default manifest if provided
    if (config.credentialManifestId) {
      this.manifests['default'] = config.credentialManifestId;
    }
  }

  /**
   * Get manifest ID by name, fallback to default
   */
  private getManifestId(manifestName?: string): string {
    if (manifestName && this.manifests[manifestName]) {
      return this.manifests[manifestName];
    }
    if (this.manifests['default']) {
      return this.manifests['default'];
    }
    throw new Error('No credential manifest ID configured');
  }

  /**
   * Execute a request with retry logic
   */
  private async executeWithRetry<T>(
    operation: () => Promise<T>,
    operationName: string
  ): Promise<T> {
    let lastError: Error | null = null;
    let delay = this.retryConfig.initialDelayMs;

    for (let attempt = 0; attempt <= this.retryConfig.maxRetries; attempt++) {
      try {
        return await operation();
      } catch (error) {
        lastError = error instanceof Error ? error : new Error(String(error));

        // Check if error is retryable
        const statusCode = (error as any)?.statusCode || (error as any)?.response?.status;
        const isRetryable = statusCode && isRetryableError(statusCode, this.retryConfig.retryableStatusCodes);

        // Don't retry on last attempt or if error is not retryable
        if (attempt === this.retryConfig.maxRetries || !isRetryable) {
          throw lastError;
        }

        // Wait before retrying
        await sleep(Math.min(delay, this.retryConfig.maxDelayMs));
        delay *= this.retryConfig.backoffMultiplier;
      }
    }

    throw lastError || new Error(`${operationName} failed after ${this.retryConfig.maxRetries} retries`);
  }

  /**
   * Issue credential with retry logic and manifest selection
   */
  async issueCredential(
    request: VerifiableCredentialRequest & { manifestName?: string }
  ): Promise<VerifiableCredentialResponse> {
    const manifestId = this.getManifestId(request.manifestName);
    
    // Create a modified request without manifestName
    const { manifestName, ...credentialRequest } = request;
    
    // Temporarily set manifest ID for this request
    const originalManifestId = (this as any).config.credentialManifestId;
    (this as any).config.credentialManifestId = manifestId;

    try {
      return await this.executeWithRetry(
        () => super.issueCredential(credentialRequest),
        'issueCredential'
      );
    } finally {
      // Restore original manifest ID
      (this as any).config.credentialManifestId = originalManifestId;
    }
  }

  /**
   * Get issuance status with retry logic
   */
  async getIssuanceStatus(requestId: string): Promise<VerifiableCredentialStatus> {
    return this.executeWithRetry(
      () => super.getIssuanceStatus(requestId),
      'getIssuanceStatus'
    );
  }

  /**
   * Verify credential with retry logic
   */
  async verifyCredential(credential: VerifiedCredential): Promise<boolean> {
    return this.executeWithRetry(
      () => super.verifyCredential(credential),
      'verifyCredential'
    );
  }

  /**
   * Create presentation request with retry logic and manifest selection
   */
  async createPresentationRequest(
    manifestName?: string,
    callbackUrl?: string
  ): Promise<VerifiableCredentialResponse> {
    const manifestId = this.getManifestId(manifestName);
    return this.executeWithRetry(
      () => super.createPresentationRequest(manifestId, callbackUrl),
      'createPresentationRequest'
    );
  }

  /**
   * Register a new manifest
   */
  registerManifest(name: string, manifestId: string): void {
    this.manifests[name] = manifestId;
  }

  /**
   * Get all registered manifests
   */
  getManifests(): Record<string, string> {
    return { ...this.manifests };
  }
}

