<?php declare(strict_types=1);

function generateTOTPSecret(int $length = 16): string {
    $validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
    $secret = '';
    for ($i = 0; $i < $length; $i++) {
        $secret .= $validChars[random_int(0, 31)];
    }
    return $secret;
}

function getTOTPQRCodeUrl(string $companyName, string $userEmail, string $secret): string {
    // rawurlencode is critical here so spaces become %20 instead of +
    $companyName = rawurlencode($companyName);
    $userEmail = rawurlencode($userEmail);
    $totpUrl = "otpauth://totp/{$companyName}:{$userEmail}?secret={$secret}&issuer={$companyName}";
    
    // Switch to a highly reliable, ad-blocker friendly API
    return "https://api.qrserver.com/v1/create-qr-code/?size=200x200&margin=1&data=" . rawurlencode($totpUrl);
}

function verifyTOTPCode(string $secret, string $code, int $discrepancy = 1): bool {
    $currentTimeSlice = floor(time() / 30);
    for ($i = -$discrepancy; $i <= $discrepancy; $i++) {
        $calculatedCode = calculateHOTP($secret, $currentTimeSlice + $i);
        if (hash_equals($calculatedCode, $code)) {
            return true;
        }
    }
    return false;
}

function calculateHOTP(string $secret, float $count): string {
    $decodedSecret = base32Decode($secret);
    $packedCount = pack('N*', 0) . pack('N*', $count);
    $hash = hash_hmac('sha1', $packedCount, $decodedSecret, true);
    $offset = ord($hash[19]) & 0xf;
    $otp = (
        ((ord($hash[$offset + 0]) & 0x7f) << 24) |
        ((ord($hash[$offset + 1]) & 0xff) << 16) |
        ((ord($hash[$offset + 2]) & 0xff) << 8) |
        (ord($hash[$offset + 3]) & 0xff)
    ) % 1000000;
    return str_pad((string)$otp, 6, '0', STR_PAD_LEFT);
}

function base32Decode(string $secret): string {
    if (empty($secret)) return '';
    $base32chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
    $base32charsFlipped = array_flip(str_split($base32chars));
    $paddingCharCount = substr_count($secret, '=');
    $allowedValues = [6, 4, 3, 1, 0];
    if (!in_array($paddingCharCount, $allowedValues)) return false;
    for ($i = 0; $i < 4; $i++) {
        if ($paddingCharCount == $allowedValues[$i] && substr($secret, -($allowedValues[$i])) != str_repeat('=', $allowedValues[$i])) return false;
    }
    $secret = str_replace('=', '', $secret);
    $secret = str_split($secret);
    $binaryString = '';
    for ($i = 0; $i < count($secret); $i += 8) {
        $x = '';
        if (!in_array($secret[$i], str_split($base32chars))) return false;
        for ($j = 0; $j < 8; $j++) {
            $x .= str_pad(base_convert((string)$base32charsFlipped[@$secret[$i + $j]], 10, 2), 5, '0', STR_PAD_LEFT);
        }
        $eightBits = str_split($x, 8);
        for ($z = 0; $z < count($eightBits); $z++) {
            $binaryString .= (($y = chr((int)base_convert($eightBits[$z], 2, 10))) || ord($y) == 48) ? $y : '';
        }
    }
    return $binaryString;
}