"""
JWT + Password 委托层单元测试（第二十一波）

覆盖：
- JWTHandler (create/verify/configure/get)
- PasswordHandler (hash/verify/needs_rehash/validate_strength/configure/get)
"""

from unittest.mock import patch

import pytest
from jwt.exceptions import InvalidTokenError

from packages.application.auth.jwt_handler import (
    JWTHandler,
    configure_jwt_handler,
    get_jwt_handler,
)
from packages.application.auth.password_handler import (
    PasswordHandler,
    configure_password_handler,
    get_password_handler,
)

SECRET_KEY = "test-secret-key-for-unit-testing-only-not-for-production"


# ============================================================
# JWTHandler
# ============================================================


class TestJWTHandler:
    """JWTHandler JWT 委托层"""

    def test_create_and_verify_access_token(self):
        """创建并验证 access_token"""
        handler = JWTHandler(secret_key=SECRET_KEY)
        token = handler.create_access_token(user_id="user-123", role="admin")

        assert isinstance(token, str)
        assert len(token) > 0

        payload = handler.verify_access_token(token)
        assert payload["sub"] == "user-123"
        assert payload["role"] == "admin"
        assert "exp" in payload
        assert "type" in payload
        assert payload["type"] == "access"

    def test_create_token_with_additional_claims(self):
        """携带额外 claims"""
        handler = JWTHandler(secret_key=SECRET_KEY)
        token = handler.create_access_token(
            user_id="user-1",
            role="user",
            additional_claims={"email": "a@b.com", "org_id": "org-1"},
        )
        payload = handler.verify_access_token(token)
        assert payload["email"] == "a@b.com"
        assert payload["org_id"] == "org-1"

    def test_create_token_default_role(self):
        """默认 role 为空字符串"""
        handler = JWTHandler(secret_key=SECRET_KEY)
        token = handler.create_access_token(user_id="user-1")
        payload = handler.verify_access_token(token)
        assert payload["role"] == ""

    def test_verify_generic_token(self):
        """verify_token 通用验证方法"""
        handler = JWTHandler(secret_key=SECRET_KEY)
        token = handler.create_access_token(user_id="user-1")
        payload = handler.verify_token(token)
        assert payload["sub"] == "user-1"

    def test_verify_invalid_token_raises(self):
        """无效 token 验证失败"""
        handler = JWTHandler(secret_key=SECRET_KEY)
        with pytest.raises(InvalidTokenError):
            handler.verify_access_token("invalid-token")

    def test_verify_wrong_secret(self):
        """用不同密钥签名的 token 验证失败"""
        handler1 = JWTHandler(secret_key="key-a")
        handler2 = JWTHandler(secret_key="key-b")

        token = handler1.create_access_token(user_id="user-1")
        with pytest.raises(InvalidTokenError):
            handler2.verify_access_token(token)

    def test_custom_algorithm(self):
        """自定义算法"""
        handler = JWTHandler(secret_key=SECRET_KEY, algorithm="HS256")
        token = handler.create_access_token(user_id="user-1")
        payload = handler.verify_access_token(token)
        assert payload["sub"] == "user-1"

    def test_custom_expire_minutes(self):
        """自定义过期时间"""
        handler = JWTHandler(secret_key=SECRET_KEY, access_token_expire_minutes=60)
        token = handler.create_access_token(user_id="user-1")
        payload = handler.verify_access_token(token)
        assert payload["sub"] == "user-1"


# ============================================================
# JWTHandler - 全局配置
# ============================================================


class TestJWTGlobalConfig:
    """JWT 全局配置与获取"""

    def test_configure_and_get(self):
        """配置后可以获取"""
        handler = configure_jwt_handler(secret_key=SECRET_KEY, access_token_expire_minutes=15)
        assert isinstance(handler, JWTHandler)

        got = get_jwt_handler()
        assert got is handler

    def test_reconfigure_replaces(self):
        """重新配置会替换"""
        h1 = configure_jwt_handler(secret_key="key-a")
        h2 = configure_jwt_handler(secret_key="key-b")
        assert h1 is not h2
        assert get_jwt_handler() is h2


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


class TestPasswordHandler:
    """PasswordHandler 密码委托层"""

    def test_hash_and_verify_correct(self):
        """哈希并验证正确密码"""
        handler = PasswordHandler()
        hashed = handler.hash_password("MySecurePass123")

        assert isinstance(hashed, str)
        assert hashed != "MySecurePass123"
        assert handler.verify_password("MySecurePass123", hashed) is True

    def test_verify_wrong_password(self):
        """验证错误密码"""
        handler = PasswordHandler()
        hashed = handler.hash_password("CorrectPass123")
        assert handler.verify_password("WrongPass456", hashed) is False

    def test_hash_is_unique_each_time(self):
        """同密码每次哈希不同（salt）"""
        handler = PasswordHandler()
        h1 = handler.hash_password("SamePass123")
        h2 = handler.hash_password("SamePass123")
        assert h1 != h2
        # 但都能验证通过
        assert handler.verify_password("SamePass123", h1)
        assert handler.verify_password("SamePass123", h2)

    def test_needs_rehash_new_hash(self):
        """新生成的哈希不需要重新计算"""
        handler = PasswordHandler()
        hashed = handler.hash_password("TestPass123")
        assert handler.needs_rehash(hashed) is False

    def test_validate_strength_strong(self):
        """强密码校验通过"""
        handler = PasswordHandler()
        ok, err = handler.validate_strength("StrongPass123")
        assert ok is True
        assert err is None or err == ""

    def test_validate_strength_too_short(self):
        """密码太短"""
        handler = PasswordHandler()
        ok, err = handler.validate_strength("Ab1")
        assert ok is False
        assert err is not None

    def test_validate_strength_no_uppercase(self):
        """缺少大写字母"""
        handler = PasswordHandler()
        ok, err = handler.validate_strength("lowercase123")
        assert ok is False
        assert err is not None

    def test_validate_strength_no_lowercase(self):
        """缺少小写字母"""
        handler = PasswordHandler()
        ok, err = handler.validate_strength("UPPERCASE123")
        assert ok is False
        assert err is not None

    def test_validate_strength_no_digit(self):
        """缺少数字"""
        handler = PasswordHandler()
        ok, err = handler.validate_strength("NoDigitHere")
        assert ok is False
        assert err is not None

    def test_hash_empty_password(self):
        """空密码哈希报错"""
        handler = PasswordHandler()
        with pytest.raises((ValueError, Exception)):
            handler.hash_password("")

    def test_custom_rounds(self):
        """自定义 rounds（用低轮次测试更快）"""
        handler = PasswordHandler(rounds=4)
        hashed = handler.hash_password("TestPass123")
        assert handler.verify_password("TestPass123", hashed)


# ============================================================
# PasswordHandler - 全局配置
# ============================================================


class TestPasswordGlobalConfig:
    """Password 全局配置与获取"""

    def test_get_default_handler(self):
        """未配置时 get 返回默认实例"""
        # 重置默认实例
        with patch("packages.application.auth.password_handler._default_handler", None):
            handler = get_password_handler()
            assert isinstance(handler, PasswordHandler)

    def test_configure_and_get(self):
        """配置后可以获取"""
        handler = configure_password_handler(rounds=4)
        assert isinstance(handler, PasswordHandler)
        got = get_password_handler()
        assert got is handler

    def test_reconfigure_replaces(self):
        """重新配置会替换"""
        h1 = configure_password_handler(rounds=4)
        h2 = configure_password_handler(rounds=6)
        assert h1 is not h2
