"""密码哈希与验证模块单元测试."""

import pytest

from packages.application.auth.password_handler import (
    PasswordHandler,
    configure_password_handler,
    get_password_handler,
)
from packages.application.auth.password_hasher import (
    PasswordHasher,
    PasswordValidator,
)

# ==================== PasswordValidator ====================


class TestPasswordValidator:
    @pytest.fixture
    def default_validator(self):
        return PasswordValidator()

    @pytest.fixture
    def strict_validator(self):
        return PasswordValidator(
            min_length=12,
            require_uppercase=True,
            require_lowercase=True,
            require_digit=True,
            require_special=True,
        )

    class TestBasicValidation:
        def test_valid_password(self, default_validator):
            ok, msg = default_validator.validate("SecurePass123")
            assert ok is True
            assert msg is None

        def test_empty_password(self, default_validator):
            ok, msg = default_validator.validate("")
            assert ok is False
            assert "empty" in msg.lower()

        def test_none_password_treated_as_empty(self, default_validator):
            # None被not判定为falsy，返回空密码错误
            ok, msg = default_validator.validate(None)
            assert ok is False
            assert "empty" in msg.lower()

    class TestMinLength:
        def test_too_short(self, default_validator):
            ok, msg = default_validator.validate("Ab1")
            assert ok is False
            assert "8" in msg

        def test_exact_min_length(self, default_validator):
            # 刚好8个字符
            ok, _ = default_validator.validate("Abcdefg1")
            assert ok is True

        def test_custom_min_length(self, strict_validator):
            ok, msg = strict_validator.validate("Short1!")
            assert ok is False
            assert "12" in msg

    class TestUppercase:
        def test_no_uppercase(self, default_validator):
            ok, msg = default_validator.validate("password123")
            assert ok is False
            assert "uppercase" in msg.lower()

        def test_with_uppercase(self, default_validator):
            ok, _ = default_validator.validate("Password123")
            assert ok is True

        def test_disabled_requirement(self):
            v = PasswordValidator(require_uppercase=False)
            ok, _ = v.validate("password123")
            assert ok is True

    class TestLowercase:
        def test_no_lowercase(self, default_validator):
            ok, msg = default_validator.validate("PASSWORD123")
            assert ok is False
            assert "lowercase" in msg.lower()

        def test_with_lowercase(self, default_validator):
            ok, _ = default_validator.validate("Password123")
            assert ok is True

        def test_disabled_requirement(self):
            v = PasswordValidator(require_lowercase=False)
            ok, _ = v.validate("PASSWORD123")
            assert ok is True

    class TestDigit:
        def test_no_digit(self, default_validator):
            ok, msg = default_validator.validate("Passworddd")
            assert ok is False
            assert "digit" in msg.lower()

        def test_with_digit(self, default_validator):
            ok, _ = default_validator.validate("Password1")
            assert ok is True

        def test_disabled_requirement(self):
            v = PasswordValidator(require_digit=False)
            ok, _ = v.validate("Passworddd")
            assert ok is True

    class TestSpecialChar:
        def test_no_special_when_not_required(self, default_validator):
            ok, _ = default_validator.validate("Password123")
            assert ok is True

        def test_no_special_when_required(self, strict_validator):
            ok, msg = strict_validator.validate("Password1234")
            assert ok is False
            assert "special" in msg.lower()

        def test_with_special(self, strict_validator):
            ok, _ = strict_validator.validate("Password123!")
            assert ok is True

        def test_various_special_chars(self, strict_validator):
            for char in "!@#$%^&*()_+-=[]{}|;:,.<>?~":
                pw = f"LongPassword1{char}"  # 13字符，含大小写数字特殊
                ok, msg = strict_validator.validate(pw)
                assert ok is True, f"special char {char} should be valid: {msg}"

    class TestAllDisabled:
        def test_all_disabled_min_length_only(self):
            v = PasswordValidator(
                min_length=1,
                require_uppercase=False,
                require_lowercase=False,
                require_digit=False,
                require_special=False,
            )
            ok, _ = v.validate("a")
            assert ok is True

        def test_all_disabled_empty_still_fails(self):
            v = PasswordValidator(
                min_length=1,
                require_uppercase=False,
                require_lowercase=False,
                require_digit=False,
                require_special=False,
            )
            ok, _ = v.validate("")
            assert ok is False


# ==================== PasswordHasher ====================


class TestPasswordHasher:
    @pytest.fixture
    def hasher(self):
        return PasswordHasher(rounds=4)  # 用最低rounds加速测试

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

        def test_hash_starts_with_bcrypt_prefix(self, hasher):
            result = hasher.hash_password("testpassword")
            assert result.startswith("$2")

        def test_hash_contains_rounds(self, hasher):
            result = hasher.hash_password("testpassword")
            parts = result.split("$")
            assert parts[2] == "04"  # bcrypt rounds格式是两位数

        def test_hash_different_salts(self, hasher):
            # 同一密码两次哈希结果不同（因为salt随机）
            h1 = hasher.hash_password("samepassword")
            h2 = hasher.hash_password("samepassword")
            assert h1 != h2

        def test_hash_empty_password_raises(self, hasher):
            with pytest.raises(ValueError, match="empty"):
                hasher.hash_password("")

        def test_hash_unicode_password(self, hasher):
            result = hasher.hash_password("密码Pass123!")
            assert isinstance(result, str)
            assert len(result) > 0

        def test_hash_long_password(self, hasher):
            long_pw = "a" * 72  # bcrypt最大72字节
            result = hasher.hash_password(long_pw)
            assert isinstance(result, str)

    class TestVerifyPassword:
        def test_verify_correct_password(self, hasher):
            hashed = hasher.hash_password("CorrectPass123")
            assert hasher.verify_password("CorrectPass123", hashed) is True

        def test_verify_wrong_password(self, hasher):
            hashed = hasher.hash_password("CorrectPass123")
            assert hasher.verify_password("WrongPass123", hashed) is False

        def test_verify_empty_password(self, hasher):
            hashed = hasher.hash_password("testpass")
            assert hasher.verify_password("", hashed) is False

        def test_verify_empty_hash(self, hasher):
            assert hasher.verify_password("testpass", "") is False

        def test_verify_invalid_hash_format(self, hasher):
            assert hasher.verify_password("testpass", "invalid-hash-format") is False

        def test_verify_none_hash(self, hasher):
            assert hasher.verify_password("testpass", None) is False

        def test_verify_unicode_password(self, hasher):
            pw = "密码Pass123!"
            hashed = hasher.hash_password(pw)
            assert hasher.verify_password(pw, hashed) is True

    class TestNeedsRehash:
        def test_same_rounds_no_rehash(self, hasher):
            hashed = hasher.hash_password("testpass")
            assert hasher.needs_rehash(hashed) is False

        def test_lower_rounds_needs_rehash(self):
            hasher_low = PasswordHasher(rounds=4)
            hashed = hasher_low.hash_password("testpass")

            hasher_high = PasswordHasher(rounds=5)
            assert hasher_high.needs_rehash(hashed) is True

        def test_higher_rounds_needs_rehash(self):
            hasher_high = PasswordHasher(rounds=5)
            hashed = hasher_high.hash_password("testpass")

            hasher_low = PasswordHasher(rounds=4)
            assert hasher_low.needs_rehash(hashed) is True

        def test_invalid_hash_no_rehash(self, hasher):
            assert hasher.needs_rehash("invalid-format") is False

        def test_empty_hash_no_rehash(self, hasher):
            assert hasher.needs_rehash("") is False

    class TestInit:
        def test_rounds_below_min_raises(self):
            with pytest.raises(ValueError):
                PasswordHasher(rounds=3)

        def test_rounds_above_max_raises(self):
            with pytest.raises(ValueError):
                PasswordHasher(rounds=32)

        def test_min_rounds_ok(self):
            h = PasswordHasher(rounds=4)
            assert h.rounds == 4

        def test_max_rounds_ok(self):
            h = PasswordHasher(rounds=31)
            assert h.rounds == 31


# ==================== PasswordHandler ====================


class TestPasswordHandler:
    @pytest.fixture
    def handler(self):
        return PasswordHandler(rounds=4)

    def test_hash_and_verify_roundtrip(self, handler):
        hashed = handler.hash_password("MySecurePass123")
        assert handler.verify_password("MySecurePass123", hashed) is True
        assert handler.verify_password("WrongPass", hashed) is False

    def test_needs_rehash(self, handler):
        # 用当前rounds哈希，不需要rehash
        hashed = handler.hash_password("testpass")
        assert handler.needs_rehash(hashed) is False

    def test_validate_strength(self, handler):
        # 强密码通过
        ok, msg = handler.validate_strength("StrongPass123")
        assert ok is True
        assert msg is None

        # 弱密码不通过
        ok, msg = handler.validate_strength("weak")
        assert ok is False
        assert msg is not None

    def test_hash_empty_raises(self, handler):
        with pytest.raises(ValueError):
            handler.hash_password("")


class TestGlobalHandler:
    def test_get_password_handler_returns_instance(self):
        # 重置全局实例
        import packages.application.auth.password_handler as ph

        ph._default_handler = None

        handler = get_password_handler()
        assert isinstance(handler, PasswordHandler)

    def test_configure_password_handler(self):
        handler = configure_password_handler(rounds=4)
        assert isinstance(handler, PasswordHandler)

        # get应该返回同一个配置好的实例
        same_handler = get_password_handler()
        assert same_handler is handler
