/**
 * CSRF Token Generator
 * Handles generation and validation of CSRF tokens
 */

import { createHash, randomBytes, timingSafeEqual } from 'crypto';
import type { CSRFConfig, CSRFToken } from './types';

export class CSRFTokenGenerator {
  private config: Required<CSRFConfig>;

  constructor(config: CSRFConfig) {
    this.config = {
      secret: config.secret,
      tokenExpiry: config.tokenExpiry || 60 * 60 * 1000, // 1 hour default
      cookieName: config.cookieName || '__csrf-token',
      headerName: config.headerName || 'X-CSRF-Token',
      fieldName: config.fieldName || '_csrf',
      secureCookie: config.secureCookie ?? true,
      httpOnlyCookie: config.httpOnlyCookie ?? true,
      sameSite: config.sameSite || 'strict'
    };
  }

  /**
   * Generate a new CSRF token for a session
   */
  generateToken(sessionId: string): CSRFToken {
    const timestamp = Date.now();
    const expiresAt = timestamp + this.config.tokenExpiry;

    // Generate random bytes for the token
    const randomData = randomBytes(32);

    // Create token payload
    const payload = JSON.stringify({
      sessionId,
      timestamp,
      expiresAt,
      random: randomData.toString('hex')
    });

    // Sign the payload with HMAC
    const signature = this.signPayload(payload);

    // Combine payload and signature
    const tokenValue = Buffer.from(payload).toString('base64') + '.' + signature;

    return {
      value: tokenValue,
      expiresAt,
      sessionId
    };
  }

  /**
   * Validate a CSRF token
   */
  validateToken(tokenValue: string, sessionId: string): { valid: boolean; error?: string; expired?: boolean } {
    try {
      // Split token into payload and signature
      const parts = tokenValue.split('.');
      if (parts.length !== 2) {
        return { valid: false, error: 'Invalid token format' };
      }

      const [payloadBase64, signature] = parts;
      if (!payloadBase64 || !signature) {
        return { valid: false, error: 'Invalid token format' };
      }
      const payload = Buffer.from(payloadBase64, 'base64').toString('utf8');

      // Verify signature
      const expectedSignature = this.signPayload(payload);
      if (!this.constantTimeCompare(signature, expectedSignature)) {
        return { valid: false, error: 'Invalid token signature' };
      }

      // Parse payload
      const tokenData = JSON.parse(payload);

      // Verify session ID
      if (tokenData.sessionId !== sessionId) {
        return { valid: false, error: 'Token session mismatch' };
      }

      // Check expiration
      if (Date.now() > tokenData.expiresAt) {
        return { valid: false, error: 'Token expired', expired: true };
      }

      return { valid: true };
    } catch (error) {
      return { valid: false, error: 'Token validation failed' };
    }
  }

  /**
   * Generate a double-submit cookie token
   */
  generateCookieToken(): string {
    const randomData = randomBytes(32);
    const timestamp = Date.now();

    const payload = JSON.stringify({
      timestamp,
      random: randomData.toString('hex')
    });

    const signature = this.signPayload(payload);
    return Buffer.from(payload).toString('base64') + '.' + signature;
  }

  /**
   * Validate double-submit cookie pattern
   */
  validateDoubleSubmit(cookieToken: string, headerToken: string): { valid: boolean; error?: string } {
    if (!cookieToken || !headerToken) {
      return { valid: false, error: 'Missing CSRF tokens' };
    }

    // For double-submit pattern, tokens should match exactly
    if (!this.constantTimeCompare(cookieToken, headerToken)) {
      return { valid: false, error: 'CSRF token mismatch' };
    }

    // Validate the token structure
    try {
      const parts = cookieToken.split('.');
      if (parts.length !== 2) {
        return { valid: false, error: 'Invalid token format' };
      }

      const [payloadBase64, signature] = parts;
      if (!payloadBase64 || !signature) {
        return { valid: false, error: 'Invalid token format' };
      }
      const payload = Buffer.from(payloadBase64, 'base64').toString('utf8');

      // Verify signature
      const expectedSignature = this.signPayload(payload);
      if (!this.constantTimeCompare(signature, expectedSignature)) {
        return { valid: false, error: 'Invalid token signature' };
      }

      return { valid: true };
    } catch (error) {
      return { valid: false, error: 'Token validation failed' };
    }
  }

  /**
   * Sign a payload using HMAC-SHA256
   */
  private signPayload(payload: string): string {
    return createHash('sha256')
      .update(this.config.secret + payload)
      .digest('hex');
  }

  /**
   * Constant-time string comparison to prevent timing attacks
   */
  private constantTimeCompare(a: string, b: string): boolean {
    if (a.length !== b.length) {
      return false;
    }

    const bufferA = Buffer.from(a);
    const bufferB = Buffer.from(b);

    return timingSafeEqual(bufferA, bufferB);
  }

  /**
   * Get configuration values
   */
  getConfig(): Required<CSRFConfig> {
    return { ...this.config };
  }
}
