<?php
namespace Misuzu;

use InvalidArgumentException;
use Index\Serialisation\Base32;

class TOTPGenerator {
    public const DIGITS = 6;
    public const INTERVAL = 30000;

    public function __construct(private string $secretKey) {}

    public static function generateKey(): string {
        return Base32::encode(random_bytes(16));
    }

    public static function timecode(?int $timestamp = null): int {
        $timestamp ??= time();
        return (int)(($timestamp * 1000) / self::INTERVAL);
    }

    public function generate(?int $timecode = null): string {
        $timecode ??= self::timecode();

        $hash = hash_hmac('sha1', pack('J', $timecode), Base32::decode($this->secretKey), true);
        $offset = ord($hash[strlen($hash) - 1]) & 0x0F;

        $bin = 0;
        $bin |= (ord($hash[$offset]) & 0x7F) << 24;
        $bin |= (ord($hash[$offset + 1]) & 0xFF) << 16;
        $bin |= (ord($hash[$offset + 2]) & 0xFF) << 8;
        $bin |= (ord($hash[$offset + 3]) & 0xFF);
        $otp = $bin % pow(10, self::DIGITS);

        return str_pad((string)$otp, self::DIGITS, '0', STR_PAD_LEFT);
    }

    public function generateRange(int $range = 1, ?int $timecode = null): array {
        if($range < 1)
            throw new InvalidArgumentException('$range must be greater than 0.');

        $timecode ??= self::timecode();
        $tokens = [$this->generate($timecode)];

        for($i = 1; $i <= $range; ++$i)
            $tokens[] = $this->generate($timecode - $i);
        for($i = 1; $i <= $range; ++$i)
            $tokens[] = $this->generate($timecode + $i);

        return $tokens;
    }
}
