/**
 * Token Validation Utility
 * Provides methods to validate JWT tokens and check expiration
 */
export interface JwtPayload {
  sub?: string;
  exp?: number;
  iat?: number;
  iss?: string;
  aud?: string | string[];
  [key: string]: unknown;
}

export class TokenValidator {
  /**
   * Check if a string looks like a valid JWT token format
   */
  static isValidFormat(token: string | null | undefined): boolean {
    if (!token || typeof token !== 'string') {
      return false;
    }

    // JWT format: header.payload.signature
    const parts = token.split('.');
    if (parts.length !== 3) {
      return false;
    }

    // Each part should be base64url encoded
    const base64UrlRegex = /^[A-Za-z0-9_-]+$/;
    return parts.every((part) => part.length > 0 && base64UrlRegex.test(part));
  }

  /**
   * Decode JWT payload without verification (for client-side use only)
   * WARNING: This does not verify the signature - always verify on server
   */
  static decodePayload(token: string): JwtPayload | null {
    if (!this.isValidFormat(token)) {
      return null;
    }

    try {
      const parts = token.split('.');
      const payload = parts[1];

      // Decode base64url
      const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
      const paddedBase64 = base64 + '='.repeat((4 - (base64.length % 4)) % 4);
      const decoded = atob(paddedBase64);

      return JSON.parse(decoded) as JwtPayload;
    } catch {
      return null;
    }
  }

  /**
   * Check if token is expired
   */
  static isExpired(token: string, bufferSeconds: number = 30): boolean {
    const payload = this.decodePayload(token);
    if (!payload || !payload.exp) {
      return true; // Treat as expired if we can't determine
    }

    const currentTime = Math.floor(Date.now() / 1000);
    // Add buffer to handle clock skew
    return payload.exp < currentTime + bufferSeconds;
  }

  /**
   * Get expiration date from token
   */
  static getExpirationDate(token: string): Date | null {
    const payload = this.decodePayload(token);
    if (!payload || !payload.exp) {
      return null;
    }

    return new Date(payload.exp * 1000);
  }

  /**
   * Get time until token expires in seconds
   */
  static getTimeUntilExpiry(token: string): number {
    const payload = this.decodePayload(token);
    if (!payload || !payload.exp) {
      return 0;
    }

    const currentTime = Math.floor(Date.now() / 1000);
    return Math.max(0, payload.exp - currentTime);
  }

  /**
   * Check if token should be refreshed (within threshold of expiry)
   */
  static shouldRefresh(token: string, thresholdSeconds: number = 300): boolean {
    const timeUntilExpiry = this.getTimeUntilExpiry(token);
    return timeUntilExpiry > 0 && timeUntilExpiry < thresholdSeconds;
  }

  /**
   * Get user ID from token
   */
  static getUserId(token: string): string | null {
    const payload = this.decodePayload(token);
    return payload?.sub || null;
  }

  /**
   * Validate token is well-formed and not expired
   * Uses 120 second buffer for clock skew tolerance
   */
  static validate(token: string | null | undefined): {
    valid: boolean;
    error?: string;
    payload?: JwtPayload;
  } {
    if (!token) {
      return { valid: false, error: 'Token is missing' };
    }

    if (!this.isValidFormat(token)) {
      return { valid: false, error: 'Invalid token format' };
    }

    const payload = this.decodePayload(token);
    if (!payload) {
      return { valid: false, error: 'Failed to decode token' };
    }

    // Use 120 second buffer for clock skew between client and server
    if (this.isExpired(token, 120)) {
      return { valid: false, error: 'Token has expired', payload };
    }

    return { valid: true, payload };
  }
}
