"""Bcrypt password hashing для DB-auth (#2550 — foundation, эпик #2549).

bcrypt тихо обрезает пароли длиннее 72 байт (UTF-8) — это silent-truncation
дыра (два разных пароля с общим 72-байтовым префиксом хешируются одинаково).
`hash_password` явно ловит это и падает с ValueError вместо тихого поведения.
`verify_password` на длинном пароле возвращает False (не raise) — сравнение
паролей не должно ронять запрос авторизации.
"""

from __future__ import annotations

import logging

import bcrypt

logger = logging.getLogger(__name__)

_BCRYPT_MAX_BYTES = 72
_BCRYPT_ROUNDS = 12


def hash_password(plain: str) -> str:
    """Хеширует пароль через bcrypt (rounds=12).

    Raises:
        ValueError: пустой пароль или пароль длиннее 72 байт в UTF-8
            (bcrypt тихо обрезает — недопустимо, см. модульный docstring).
    """
    if not plain:
        raise ValueError("password must not be empty")

    encoded = plain.encode("utf-8")
    if len(encoded) > _BCRYPT_MAX_BYTES:
        raise ValueError(
            f"password too long: {len(encoded)} bytes (bcrypt max {_BCRYPT_MAX_BYTES})"
        )

    salt = bcrypt.gensalt(rounds=_BCRYPT_ROUNDS)
    hashed = bcrypt.hashpw(encoded, salt)
    return hashed.decode("utf-8")


def verify_password(plain: str, hashed: str) -> bool:
    """Сверяет пароль с bcrypt-хешем.

    Пустой пароль или пароль длиннее 72 байт в UTF-8 → False (не raise —
    verify — это false/true проверка на этапе логина, а не валидация ввода).
    """
    if not plain or not hashed:
        return False

    encoded = plain.encode("utf-8")
    if len(encoded) > _BCRYPT_MAX_BYTES:
        return False

    try:
        return bcrypt.checkpw(encoded, hashed.encode("utf-8"))
    except (ValueError, TypeError) as e:
        # Malformed hash (напр. не-bcrypt строка в БД) — не должно ронять login.
        logger.warning("verify_password: malformed hash rejected: %s", e)
        return False
