"""
密码哈希工具类
使用 bcrypt 安全存储密码
"""

from typing import Optional

import bcrypt


class PasswordHasher:
    """密码哈希服务"""

    def __init__(self, rounds: int = 12):
        """
        初始化密码哈希器

        Args:
            rounds: bcrypt cost factor（默认 12，推荐范围 10-14）
                   值越大越安全，但计算时间越长
        """
        if rounds < 4 or rounds > 31:
            raise ValueError("rounds must be between 4 and 31")

        self.rounds = rounds

    def hash_password(self, password: str) -> str:
        """
        哈希密码

        Args:
            password: 明文密码

        Returns:
            bcrypt 哈希字符串（包含 salt）

        Raises:
            ValueError: 密码为空
        """
        if not password:
            raise ValueError("Password cannot be empty")

        # bcrypt 需要 bytes
        password_bytes = password.encode("utf-8")

        # 生成 salt 并哈希
        salt = bcrypt.gensalt(rounds=self.rounds)
        hashed = bcrypt.hashpw(password_bytes, salt)

        # 返回字符串（数据库存储）
        return hashed.decode("utf-8")

    def verify_password(self, password: str, hashed_password: str) -> bool:
        """
        验证密码

        Args:
            password: 明文密码
            hashed_password: 存储的哈希密码

        Returns:
            True 如果密码正确，否则 False
        """
        if not password or not hashed_password:
            return False

        try:
            password_bytes = password.encode("utf-8")
            hashed_bytes = hashed_password.encode("utf-8")

            return bcrypt.checkpw(password_bytes, hashed_bytes)
        except Exception:
            # 哈希格式错误或其他异常，返回 False
            return False

    def needs_rehash(self, hashed_password: str) -> bool:
        """
        检查哈希是否需要重新计算
        （当 cost factor 改变时需要重新哈希）

        Args:
            hashed_password: 存储的哈希密码

        Returns:
            True 如果需要重新哈希
        """
        try:
            hashed_bytes = hashed_password.encode("utf-8")
            current_rounds = bcrypt.getsalt(hashed_bytes)

            # 提取当前的 cost factor
            # bcrypt hash 格式: $2b$rounds$salt+hash
            parts = hashed_password.split("$")
            if len(parts) >= 3:
                stored_rounds = int(parts[2])
                return stored_rounds != self.rounds

            return False
        except Exception:
            return False


class PasswordValidator:
    """密码强度验证器"""

    def __init__(
        self,
        min_length: int = 8,
        require_uppercase: bool = True,
        require_lowercase: bool = True,
        require_digit: bool = True,
        require_special: bool = False,
    ):
        """
        初始化密码验证器

        Args:
            min_length: 最小长度
            require_uppercase: 是否要求大写字母
            require_lowercase: 是否要求小写字母
            require_digit: 是否要求数字
            require_special: 是否要求特殊字符
        """
        self.min_length = min_length
        self.require_uppercase = require_uppercase
        self.require_lowercase = require_lowercase
        self.require_digit = require_digit
        self.require_special = require_special

    def validate(self, password: str) -> tuple[bool, Optional[str]]:
        """
        验证密码强度

        Args:
            password: 明文密码

        Returns:
            (是否有效, 错误信息)
        """
        if not password:
            return False, "Password cannot be empty"

        if len(password) < self.min_length:
            return False, f"Password must be at least {self.min_length} characters"

        if self.require_uppercase and not any(c.isupper() for c in password):
            return False, "Password must contain at least one uppercase letter"

        if self.require_lowercase and not any(c.islower() for c in password):
            return False, "Password must contain at least one lowercase letter"

        if self.require_digit and not any(c.isdigit() for c in password):
            return False, "Password must contain at least one digit"

        if self.require_special:
            special_chars = "!@#$%^&*()_+-=[]{}|;:,.<>?~"
            if not any(c in special_chars for c in password):
                return False, "Password must contain at least one special character"

        return True, None


# 全局实例
password_hasher = PasswordHasher(rounds=12)
password_validator = PasswordValidator(
    min_length=8,
    require_uppercase=True,
    require_lowercase=True,
    require_digit=True,
    require_special=False,
)
