/**
 * PayPal Certificate Management and Verification
 *
 * Handles certificate download, caching, and signature verification
 * for PayPal webhook events
 */

import * as https from 'https';
import * as crypto from 'crypto';
import crc32 from 'crc-32';
import { logger } from './logger';

interface CertCacheEntry {
  cert: string;
  timestamp: number;
}

// Certificate cache with 24-hour TTL
const certCache = new Map<string, CertCacheEntry>();
const CERT_CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours

/**
 * Validate that the certificate URL is from PayPal
 *
 * @param certUrl Certificate URL from webhook headers
 * @returns True if URL is valid PayPal domain
 */
function validateCertUrl(certUrl: string): boolean {
  try {
    const url = new URL(certUrl);

    // Must use HTTPS
    if (url.protocol !== 'https:') {
      logger.warn('Certificate URL must use HTTPS', { certUrl });
      return false;
    }

    // Must be from PayPal domain
    const validDomains = [
      'api.paypal.com',
      'api-m.paypal.com',
      'api.sandbox.paypal.com',
      'api-m.sandbox.paypal.com',
    ];

    if (!validDomains.includes(url.hostname)) {
      logger.warn('Certificate URL not from valid PayPal domain', {
        certUrl,
        hostname: url.hostname,
      });
      return false;
    }

    return true;
  } catch (error) {
    logger.error('Invalid certificate URL format', { certUrl, error });
    return false;
  }
}

/**
 * Download certificate from PayPal
 *
 * @param certUrl Certificate URL from webhook headers
 * @returns Certificate content (PEM format)
 */
const MAX_CERT_SIZE = 10 * 1024; // 10 KB

function downloadCertificate(certUrl: string): Promise<string> {
  return new Promise((resolve, reject) => {
    https
      .get(certUrl, { timeout: 10000 }, (res) => {
        if (res.statusCode !== 200) {
          reject(
            new Error(`Failed to download certificate: HTTP ${res.statusCode}`)
          );
          return;
        }

        let data = '';
        res.on('data', (chunk) => {
          data += chunk;
          if (data.length > MAX_CERT_SIZE) {
            res.destroy();
            reject(new Error(`Certificate exceeds maximum size of ${MAX_CERT_SIZE} bytes`));
          }
        });

        res.on('end', () => {
          resolve(data);
        });
      })
      .on('error', (error) => {
        reject(new Error(`Certificate download failed: ${error.message}`));
      })
      .on('timeout', () => {
        reject(new Error('Certificate download timeout'));
      });
  });
}

/**
 * Get certificate from cache or download it
 *
 * @param certUrl Certificate URL from webhook headers
 * @returns Certificate content (PEM format)
 */
async function getCertificate(certUrl: string): Promise<string> {
  // Check cache
  const cached = certCache.get(certUrl);
  if (cached) {
    const age = Date.now() - cached.timestamp;
    if (age < CERT_CACHE_TTL_MS) {
      logger.debug('Using cached PayPal certificate', {
        certUrl,
        ageMs: age,
      });
      return cached.cert;
    } else {
      // Expired, remove from cache
      certCache.delete(certUrl);
      logger.debug('Certificate cache expired', { certUrl });
    }
  }

  // Download certificate
  logger.info('Downloading PayPal certificate', { certUrl });
  const cert = await downloadCertificate(certUrl);

  // Cache it
  certCache.set(certUrl, {
    cert,
    timestamp: Date.now(),
  });

  return cert;
}

/**
 * Verify PayPal webhook signature
 *
 * Implements PayPal's webhook signature verification algorithm:
 * 1. Validates certificate URL is from PayPal
 * 2. Downloads and caches certificate
 * 3. Constructs expected message from webhook headers and body
 * 4. Verifies signature using certificate's public key
 *
 * @param headers Webhook request headers
 * @param body Raw webhook request body (string)
 * @returns True if signature is valid, false otherwise
 */
export async function verifyPayPalWebhookSignature(
  headers: Record<string, string | string[] | undefined>,
  body: string,
  webhookId: string
): Promise<boolean> {
  try {
    // Extract PayPal headers
    const transmissionId = headers['paypal-transmission-id'] as string;
    const transmissionTime = headers['paypal-transmission-time'] as string;
    const transmissionSig = headers['paypal-transmission-sig'] as string;
    const certUrl = headers['paypal-cert-url'] as string;
    const authAlgo = headers['paypal-auth-algo'] as string;

    // Validate required headers
    if (!transmissionId || !transmissionTime || !transmissionSig || !certUrl || !authAlgo) {
      logger.warn('Missing PayPal webhook headers', {
        hasTransmissionId: !!transmissionId,
        hasTransmissionTime: !!transmissionTime,
        hasTransmissionSig: !!transmissionSig,
        hasCertUrl: !!certUrl,
        hasAuthAlgo: !!authAlgo,
      });
      return false;
    }

    // Validate certificate URL
    if (!validateCertUrl(certUrl)) {
      return false;
    }

    // Get certificate (from cache or download)
    const cert = await getCertificate(certUrl);

    // Construct expected message for verification
    // Format: transmission_id|transmission_time|webhook_id|crc32(body)
    const crcValue = crc32.str(body);
    const bodyHash = String(crcValue);

    const expectedMessage = `${transmissionId}|${transmissionTime}|${webhookId}|${bodyHash}`;

    logger.debug('Verifying PayPal webhook signature', {
      transmissionId,
      transmissionTime,
      authAlgo,
      expectedMessageLength: expectedMessage.length,
    });

    // Verify signature using certificate's public key
    // PayPal uses SHA256withRSA algorithm
    const verify = crypto.createVerify('RSA-SHA256');
    verify.update(expectedMessage);

    const isValid = verify.verify(cert, transmissionSig, 'base64');

    if (!isValid) {
      logger.warn('PayPal webhook signature verification failed', {
        transmissionId,
        certUrl,
        authAlgo,
      });
    } else {
      logger.debug('PayPal webhook signature verified successfully', {
        transmissionId,
      });
    }

    return isValid;
  } catch (error) {
    logger.error('PayPal webhook signature verification error', {
      error: error instanceof Error ? error.message : 'Unknown error',
      stack: error instanceof Error ? error.stack : undefined,
    });
    // Fail closed: reject webhook on any error
    return false;
  }
}

/**
 * Clear certificate cache (for testing or manual invalidation)
 */
export function clearCertCache(): void {
  const size = certCache.size;
  certCache.clear();
  logger.info('PayPal certificate cache cleared', { entriesCleared: size });
}

/**
 * Get certificate cache statistics
 */
export function getCertCacheStats(): {
  size: number;
  entries: Array<{ url: string; ageMs: number }>;
} {
  const now = Date.now();
  const entries = Array.from(certCache.entries()).map(([url, entry]) => ({
    url,
    ageMs: now - entry.timestamp,
  }));

  return {
    size: certCache.size,
    entries,
  };
}
