<?php
/**
 * Email Verification Utility
 * 
 * Handles generation, validation, and management of email verification tokens
 */

class EmailVerification {
    // Token expiration time in seconds (24 hours)
    const TOKEN_EXPIRY = 86400;
    
    // Token length in bytes (will be longer when base64 encoded)
    const TOKEN_LENGTH = 32;
    
    /**
     * Generate a new verification token
     * 
     * @return array [token, hashedToken, expiresAt]
     */
    public static function generateToken() {
        // Generate cryptographically secure random bytes
        $randomBytes = random_bytes(self::TOKEN_LENGTH);
        
        // Convert to URL-safe base64
        $token = rtrim(strtr(base64_encode($randomBytes), '+/', '-_'), '=');
        
        // Create a hashed version for storage
        $hashedToken = self::hashToken($token);
        
        // Set expiration time
        $expiresAt = new DateTime();
        $expiresAt->modify('+' . self::TOKEN_EXPIRY . ' seconds');
        
        return [
            'token' => $token,
            'hashed_token' => $hashedToken,
            'expires_at' => $expiresAt->format('Y-m-d H:i:s')
        ];
    }
    
    /**
     * Hash a token for storage
     * 
     * @param string $token The raw token
     * @return string Hashed token
     */
    public static function hashToken($token) {
        return password_hash($token, PASSWORD_BCRYPT, ['cost' => 12]);
    }
    
    /**
     * Verify a token against a hash
     * 
     * @param string $token The raw token
     * @param string $hash The hashed token
     * @return bool True if valid, false otherwise
     */
    public static function verifyToken($token, $hash) {
        return password_verify($token, $hash);
    }
    
    /**
     * Check if a token is expired
     * 
     * @param string $expiresAt MySQL datetime string
     * @return bool True if expired, false otherwise
     */
    public static function isTokenExpired($expiresAt) {
        $expiryDate = new DateTime($expiresAt);
        $now = new DateTime();
        return $now > $expiryDate;
    }
    
    /**
     * Store a verification token in the database
     * 
     * @param PDO $pdo Database connection
     * @param int $userId User ID
     * @param string $hashedToken Hashed token
     * @param string $expiresAt Expiration datetime
     * @return bool True on success, false on failure
     */
    public static function storeToken($pdo, $userId, $hashedToken, $expiresAt) {
        try {
            // Ensure table exists
            $tableExists = $pdo->query("SHOW TABLES LIKE 'email_verification_tokens'")->rowCount() > 0;
            if (!$tableExists) {
                $pdo->exec("CREATE TABLE IF NOT EXISTS `email_verification_tokens` (
                    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
                    `user_id` BIGINT UNSIGNED NOT NULL,
                    `token` VARCHAR(255) NOT NULL,
                    `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
                    `expires_at` DATETIME NOT NULL,
                    `used_at` DATETIME NULL DEFAULT NULL,
                    PRIMARY KEY (`id`),
                    INDEX `idx_user_id` (`user_id`),
                    INDEX `idx_token_expires` (`expires_at`)
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;");
            }

            // Invalidate any existing tokens for this user
            $stmt = $pdo->prepare("
                UPDATE email_verification_tokens 
                SET used_at = NOW() 
                WHERE user_id = ? AND used_at IS NULL
            ");
            $stmt->execute([$userId]);

            // Insert new token
            $stmt = $pdo->prepare("INSERT INTO email_verification_tokens (user_id, token, expires_at) VALUES (?, ?, ?)");
            return $stmt->execute([$userId, $hashedToken, $expiresAt]);
        } catch (PDOException $e) {
            error_log("Error in storeToken: " . $e->getMessage() . "\n" . $e->getTraceAsString());
            return false;
        }
    }
    
    /**
     * Get a valid verification token for a user
     * 
     * @param PDO $pdo Database connection
     * @param string $token Raw token
     * @return array|false Token data or false if invalid
     */
    public static function getValidToken($pdo, $token) {
        try {
            // Fetch recent unused, unexpired tokens and verify against hashed token using password_verify
            $stmt = $pdo->prepare("SELECT t.*, u.email, u.username 
                FROM email_verification_tokens t
                JOIN users u ON t.user_id = u.id
                WHERE t.used_at IS NULL 
                  AND t.expires_at > NOW()
                ORDER BY t.created_at DESC
                LIMIT 500");
            $stmt->execute();
            $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
            foreach ($rows as $row) {
                if (!empty($row['token']) && password_verify($token, $row['token'])) {
                    return $row;
                }
            }
            return false;
        } catch (PDOException $e) {
            error_log('Error fetching verification token: ' . $e->getMessage());
            return false;
        }
    }
    
    /**
     * Mark a token as used
     * 
     * @param PDO $pdo Database connection
     * @param int $tokenId Token ID
     * @return bool True on success, false on failure
     */
    public static function markTokenUsed($pdo, $tokenId) {
        try {
            $stmt = $pdo->prepare("
                UPDATE email_verification_tokens 
                SET used_at = NOW() 
                WHERE id = ? AND used_at IS NULL
            ");
            
            return $stmt->execute([$tokenId]);
        } catch (PDOException $e) {
            error_log('Error marking token as used: ' . $e->getMessage());
            return false;
        }
    }
}
