<?php

/**
 * Клас для хешування даних
 * Хешування паролів, створення токенів та перевірочних кодів
 *
 * @package Flowaxy\Infrastructure\Security
 * @version 1.0.0 Alpha prerelease
 */

declare(strict_types=1);

namespace Flowaxy\Infrastructure\Security;

use Flowaxy\Contracts\Security\HashInterface;

final class Hash implements HashInterface
{
    /**
     * Створити хеш
     *
     * @param string $value Значення для хешування
     * @param array<string, mixed> $options Опції (algorithm, options)
     * @return string Хеш
     */
    public function make(string $value, array $options = []): string
    {
        $algorithm = $options['algorithm'] ?? PASSWORD_DEFAULT;
        $algorithmOptions = $options['options'] ?? null;

        return $algorithmOptions !== null
            ? password_hash($value, $algorithm, $algorithmOptions)
            : password_hash($value, $algorithm);
    }

    /**
     * Перевірити хеш
     *
     * @param string $value Значення для перевірки
     * @param string $hash Хеш для перевірки
     * @return bool
     */
    public function check(string $value, string $hash): bool
    {
        return password_verify($value, $hash);
    }

    /**
     * Перевірити, чи потрібно перехешувати
     *
     * @param string $hash Хеш для перевірки
     * @param array<string, mixed> $options Опції (algorithm, options)
     * @return bool
     */
    public function needsRehash(string $hash, array $options = []): bool
    {
        $algorithm = $options['algorithm'] ?? PASSWORD_DEFAULT;
        $algorithmOptions = $options['options'] ?? null;

        return $algorithmOptions !== null
            ? password_needs_rehash($hash, $algorithm, $algorithmOptions)
            : password_needs_rehash($hash, $algorithm);
    }

    /**
     * Отримати інформацію про хеш
     *
     * @param string $hash Хеш
     * @return array<string, mixed> Інформація про хеш
     */
    public function info(string $hash): array
    {
        return password_get_info($hash);
    }

    /**
     * Створення випадкового токена
     *
     * @param int $length Довжина токена в байтах (за замовчуванням 32)
     * @return string Токен в hex форматі
     */
    public static function token(int $length = 32): string
    {
        return bin2hex(random_bytes($length));
    }

    /**
     * Створення випадкового рядка
     *
     * @param int $length Довжина рядка
     * @param string $charset Набір символів
     * @return string
     */
    public static function randomString(int $length = 32, string $charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): string
    {
        $charsetLength = strlen($charset);
        $result = '';

        for ($i = 0; $i < $length; $i++) {
            $result .= $charset[random_int(0, $charsetLength - 1)];
        }

        return $result;
    }

    /**
     * Створення MD5 хеша
     *
     * @param string $data Дані для хешування
     * @param bool $rawOutput Повертати сирі бінарні дані
     * @return string
     */
    public static function md5(string $data, bool $rawOutput = false): string
    {
        return md5($data, $rawOutput);
    }

    /**
     * Створення SHA1 хеша
     *
     * @param string $data Дані для хешування
     * @param bool $rawOutput Повертати сирі бінарні дані
     * @return string
     */
    public static function sha1(string $data, bool $rawOutput = false): string
    {
        return sha1($data, $rawOutput);
    }

    /**
     * Створення SHA256 хеша
     *
     * @param string $data Дані для хешування
     * @param bool $rawOutput Повертати сирі бінарні дані
     * @return string
     */
    public static function sha256(string $data, bool $rawOutput = false): string
    {
        return hash('sha256', $data, $rawOutput);
    }

    /**
     * Створення SHA512 хеша
     *
     * @param string $data Дані для хешування
     * @param bool $rawOutput Повертати сирі бінарні дані
     * @return string
     */
    public static function sha512(string $data, bool $rawOutput = false): string
    {
        return hash('sha512', $data, $rawOutput);
    }

    /**
     * Створення HMAC підпису
     *
     * @param string $data Дані
     * @param string $key Секретний ключ
     * @param string $algorithm Алгоритм (sha256, sha512 і т.д.)
     * @param bool $rawOutput Повертати сирі бінарні дані
     * @return string
     */
    public static function hmac(string $data, string $key, string $algorithm = 'sha256', bool $rawOutput = false): string
    {
        return hash_hmac($algorithm, $data, $key, $rawOutput);
    }

    /**
     * Створення перевірочного коду (наприклад, для email підтвердження)
     *
     * @param int $length Довжина коду
     * @param bool $numeric Тільки цифри
     * @return string
     */
    public static function code(int $length = 6, bool $numeric = true): string
    {
        return $numeric
            ? self::randomString($length, '0123456789')
            : self::randomString($length, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789');
    }

    /**
     * Порівняння хешів з захистом від timing attack
     *
     * @param string $knownString Відомий рядок
     * @param string $userString Користувацький рядок
     * @return bool
     */
    public static function equals(string $knownString, string $userString): bool
    {
        // Нормалізуємо рядки перед порівнянням
        $knownString = (string)$knownString;
        $userString = (string)$userString;

        // Перевіряємо довжину перед викликом hash_equals
        if (strlen($knownString) !== strlen($userString)) {
            return false;
        }

        // Використовуємо hash_equals для безпечного порівняння
        $result = hash_equals($knownString, $userString);

        // Якщо hash_equals повертає false, але рядки ідентичні при стрічковому порівнянні,
        // це може бути проблема з hash_equals (рідкісний випадок)
        if (!$result && $knownString === $userString) {
            // Використовуємо стрічкове порівняння як fallback
            return true;
        }

        return $result;
    }
}
