"""密码哈希与验证单元测试 — wave131."""

from __future__ import annotations

import pytest

from packages.application.auth.password_hasher import (
    PasswordHasher,
    PasswordValidator,
    password_hasher,
    password_validator,
)

# ── PasswordHasher 初始化 ───────────────────────────────────────────────────


class TestPasswordHasherInit:
    def test_default_rounds(self):
        hasher = PasswordHasher()
        assert hasher.rounds == 12

    def test_custom_rounds(self):
        hasher = PasswordHasher(rounds=8)
        assert hasher.rounds == 8

    def test_min_rounds(self):
        hasher = PasswordHasher(rounds=4)
        assert hasher.rounds == 4

    def test_max_rounds(self):
        hasher = PasswordHasher(rounds=31)
        assert hasher.rounds == 31

    def test_rounds_below_min_raises(self):
        with pytest.raises(ValueError, match="rounds must be between"):
            PasswordHasher(rounds=3)

    def test_rounds_above_max_raises(self):
        with pytest.raises(ValueError, match="rounds must be between"):
            PasswordHasher(rounds=32)

    def test_rounds_zero_raises(self):
        with pytest.raises(ValueError):
            PasswordHasher(rounds=0)

    def test_rounds_negative_raises(self):
        with pytest.raises(ValueError):
            PasswordHasher(rounds=-5)


# ── hash_password ──────────────────────────────────────────────────────────


# 用 rounds=4 加速测试
def _make_hasher():
    return PasswordHasher(rounds=4)


class TestHashPassword:
    def test_returns_string(self):
        hasher = _make_hasher()
        result = hasher.hash_password("testpassword")
        assert isinstance(result, str)
        assert len(result) > 0

    def test_empty_password_raises(self):
        hasher = _make_hasher()
        with pytest.raises(ValueError, match="Password cannot be empty"):
            hasher.hash_password("")

    def test_none_password_raises(self):
        hasher = _make_hasher()
        with pytest.raises(ValueError):
            hasher.hash_password(None)  # type: ignore

    def test_bcrypt_format(self):
        """bcrypt hash 格式: $2b$rounds$..."""
        hasher = _make_hasher()
        result = hasher.hash_password("test")
        assert result.startswith("$2b$04$")

    def test_same_password_different_hash(self):
        """每次哈希应该生成不同的 salt."""
        hasher = _make_hasher()
        h1 = hasher.hash_password("samepassword")
        h2 = hasher.hash_password("samepassword")
        assert h1 != h2

    def test_hash_contains_salt(self):
        """bcrypt hash 长度固定约 60 字符."""
        hasher = _make_hasher()
        result = hasher.hash_password("test")
        assert len(result) >= 59  # bcrypt 标准长度

    def test_long_password(self):
        hasher = _make_hasher()
        long_pw = "x" * 100
        result = hasher.hash_password(long_pw)
        assert isinstance(result, str)
        assert len(result) > 0

    def test_unicode_password(self):
        hasher = _make_hasher()
        result = hasher.hash_password("密码测试_🎉_123")
        assert isinstance(result, str)
        assert len(result) > 0

    def test_special_chars_password(self):
        hasher = _make_hasher()
        result = hasher.hash_password("p@ssw0rd!#$%^&*()")
        assert isinstance(result, str)


# ── verify_password ─────────────────────────────────────────────────────────


class TestVerifyPassword:
    def test_correct_password_returns_true(self):
        hasher = _make_hasher()
        pw = "MySecureP@ss123"
        hashed = hasher.hash_password(pw)
        assert hasher.verify_password(pw, hashed) is True

    def test_wrong_password_returns_false(self):
        hasher = _make_hasher()
        hashed = hasher.hash_password("correct_password")
        assert hasher.verify_password("wrong_password", hashed) is False

    def test_empty_password_returns_false(self):
        hasher = _make_hasher()
        hashed = hasher.hash_password("test")
        assert hasher.verify_password("", hashed) is False

    def test_empty_hash_returns_false(self):
        hasher = _make_hasher()
        assert hasher.verify_password("test", "") is False

    def test_none_password_returns_false(self):
        hasher = _make_hasher()
        hashed = hasher.hash_password("test")
        assert hasher.verify_password(None, hashed) is False  # type: ignore

    def test_invalid_hash_format_returns_false(self):
        hasher = _make_hasher()
        assert hasher.verify_password("test", "not-a-valid-hash") is False

    def test_case_sensitive(self):
        hasher = _make_hasher()
        hashed = hasher.hash_password("Password")
        assert hasher.verify_password("password", hashed) is False
        assert hasher.verify_password("PASSWORD", hashed) is False
        assert hasher.verify_password("Password", hashed) is True

    def test_whitespace_matters(self):
        hasher = _make_hasher()
        hashed = hasher.hash_password("password")
        assert hasher.verify_password(" password", hashed) is False
        assert hasher.verify_password("password ", hashed) is False

    def test_unicode_roundtrip(self):
        hasher = _make_hasher()
        pw = "密码_测试_🎉"
        hashed = hasher.hash_password(pw)
        assert hasher.verify_password(pw, hashed) is True


# ── needs_rehash ────────────────────────────────────────────────────────────


class TestNeedsRehash:
    def test_same_rounds_no_rehash(self):
        hasher = _make_hasher()  # rounds=4
        hashed = hasher.hash_password("test")
        assert hasher.needs_rehash(hashed) is False

    def test_different_rounds_needs_rehash(self):
        hasher4 = PasswordHasher(rounds=4)
        hashed = hasher4.hash_password("test")
        hasher6 = PasswordHasher(rounds=6)
        assert hasher6.needs_rehash(hashed) is True

    def test_higher_rounds_needs_rehash(self):
        hasher4 = PasswordHasher(rounds=4)
        hashed = hasher4.hash_password("test")
        hasher10 = PasswordHasher(rounds=10)
        assert hasher10.needs_rehash(hashed) is True

    def test_lower_rounds_also_needs_rehash(self):
        hasher10 = PasswordHasher(rounds=10)
        # 用 rounds=10 的 hasher 生成的 hash，用 rounds=4 的检查应该也要重算（因为 cost 不同）
        hashed = hasher10.hash_password("test")
        hasher4 = PasswordHasher(rounds=4)
        assert hasher4.needs_rehash(hashed) is True

    def test_invalid_hash_returns_false(self):
        hasher = _make_hasher()
        assert hasher.needs_rehash("invalid-hash") is False

    def test_empty_hash_returns_false(self):
        hasher = _make_hasher()
        assert hasher.needs_rehash("") is False


# ── PasswordValidator 初始化 ────────────────────────────────────────────────


class TestPasswordValidatorInit:
    def test_default_settings(self):
        validator = PasswordValidator()
        assert validator.min_length == 8
        assert validator.require_uppercase is True
        assert validator.require_lowercase is True
        assert validator.require_digit is True
        assert validator.require_special is False

    def test_custom_settings(self):
        validator = PasswordValidator(
            min_length=12,
            require_uppercase=False,
            require_lowercase=False,
            require_digit=False,
            require_special=True,
        )
        assert validator.min_length == 12
        assert validator.require_uppercase is False
        assert validator.require_special is True


# ── PasswordValidator.validate ──────────────────────────────────────────────


class TestPasswordValidator:
    def setup_method(self):
        self.validator = PasswordValidator()  # 默认 8 位 + 大小写 + 数字

    def test_strong_password_passes(self):
        valid, error = self.validator.validate("MyStr0ngP@ss")
        assert valid is True
        assert error is None

    def test_empty_password_fails(self):
        valid, error = self.validator.validate("")
        assert valid is False
        assert "empty" in error.lower()

    def test_too_short_fails(self):
        valid, error = self.validator.validate("Abc123")  # 6 chars
        assert valid is False
        assert "at least" in error.lower()
        assert "8" in error

    def test_exactly_min_length_passes(self):
        valid, error = self.validator.validate("Abcd1234")  # 8 chars
        assert valid is True

    def test_no_uppercase_fails(self):
        valid, error = self.validator.validate("lowercase123")
        assert valid is False
        assert "uppercase" in error.lower()

    def test_no_lowercase_fails(self):
        valid, error = self.validator.validate("UPPERCASE123")
        assert valid is False
        assert "lowercase" in error.lower()

    def test_no_digit_fails(self):
        valid, error = self.validator.validate("NoDigitsHere")
        assert valid is False
        assert "digit" in error.lower()

    def test_special_char_not_required_by_default(self):
        # 默认不要求特殊字符，没有也通过
        valid, error = self.validator.validate("GoodPass123")
        assert valid is True

    def test_special_char_required_and_missing(self):
        validator = PasswordValidator(require_special=True)
        valid, error = validator.validate("GoodPass123")
        assert valid is False
        assert "special character" in error.lower()

    def test_special_char_required_and_present(self):
        validator = PasswordValidator(require_special=True)
        valid, error = validator.validate("GoodPass123!")
        assert valid is True

    def test_all_special_chars_accepted(self):
        validator = PasswordValidator(require_special=True)
        specials = "!@#$%^&*()_+-=[]{}|;:,.<>?~"
        for c in specials:
            pw = f"Pass123{c}"
            valid, _ = validator.validate(pw)
            assert valid is True, f"Special char '{c}' should be accepted"

    def test_minimal_valid_password(self):
        """刚好满足所有要求的最短密码."""
        valid, _ = self.validator.validate("Ab1cdefg")  # 8 chars: 1 upper, 1 lower, 1 digit
        assert valid is True

    def test_only_digits_fails(self):
        valid, _ = self.validator.validate("12345678")
        assert valid is False

    def test_only_letters_fails(self):
        valid, _ = self.validator.validate("Abcdefgh")
        assert valid is False  # 没有数字

    def test_long_password_passes(self):
        valid, _ = self.validator.validate("VeryLongPassword123WithManyCharacters")
        assert valid is True

    def test_unicode_password_passes(self):
        """Unicode 字符计数正确."""
        valid, error = self.validator.validate("密码Abc12345")
        assert valid is True

    def test_whitespace_password_passes_if_meets_req(self):
        """包含空格的密码（如果满足其他要求）应该通过."""
        valid, _ = self.validator.validate("Pass word 123")
        assert valid is True


# ── 全局实例 ────────────────────────────────────────────────────────────────


class TestGlobalInstances:
    def test_global_hasher_exists(self):
        assert password_hasher is not None
        assert isinstance(password_hasher, PasswordHasher)
        assert password_hasher.rounds == 12

    def test_global_validator_exists(self):
        assert password_validator is not None
        assert isinstance(password_validator, PasswordValidator)

    def test_global_hasher_works(self):
        hashed = password_hasher.hash_password("test_global")
        assert password_hasher.verify_password("test_global", hashed) is True

    def test_global_validator_works(self):
        valid, _ = password_validator.validate("TestPass123")
        assert valid is True
