import asyncio
from types import SimpleNamespace

import bcrypt
import pytest

from app.api.endpoints import user as user_endpoint
from app.application.security.token import (
    PasswordTooLongError,
    get_password_hash,
    verify_password,
)


PASSLIB_BCRYPT_HASH = "$2b$12$6QiVIML7x3T.F/p6cuFjLuMvFumE1V4OZpvhGVgCwaSoBE7lHlMle"


def test_password_hash_uses_existing_bcrypt_contract():
    """新密码保持 $2b$、cost 12，并可由同一包装正确验证。"""
    hashed_password = get_password_hash("new-password")

    assert hashed_password.startswith("$2b$12$")
    assert verify_password("new-password", hashed_password) is True
    assert verify_password("wrong-password", hashed_password) is False


def test_verify_password_accepts_existing_passlib_bcrypt_hash():
    """不依赖 Passlib 时仍能验证既有 bcrypt 密码哈希。"""
    assert verify_password("existing-passlib-password", PASSLIB_BCRYPT_HASH) is True


def test_get_password_hash_rejects_more_than_72_utf8_bytes():
    """bcrypt 不得静默截断 UTF-8 编码后超过 72 字节的新密码。"""
    with pytest.raises(PasswordTooLongError, match="72 字节"):
        get_password_hash("a" * 73)

    with pytest.raises(PasswordTooLongError, match="72 字节"):
        get_password_hash("密" * 25)


def test_get_password_hash_accepts_exactly_72_utf8_bytes():
    """UTF-8 编码后恰好 72 字节的密码仍属于有效输入。"""
    password = "密" * 24

    assert verify_password(password, get_password_hash(password)) is True


def test_verify_password_preserves_legacy_long_password_access():
    """既有超长密码即使在多字节字符中间截断也应保持可登录。"""
    password = "a" * 70 + "密"
    hashed_password = bcrypt.hashpw(
        password.encode("utf-8")[:72], bcrypt.gensalt(rounds=4)
    ).decode("ascii")

    assert verify_password(password, hashed_password) is True


def test_verify_password_rejects_malformed_hash():
    """损坏的数据库哈希应按认证失败处理。"""
    assert verify_password("password", "not-a-bcrypt-hash") is False


class _CreateUserInput:
    """提供新增用户接口所需的最小输入契约。"""

    name = "new-user"

    @staticmethod
    def model_dump():
        """返回包含超长多字节密码的用户数据。"""
        return {
            "name": "new-user",
            "email": None,
            "password": "Ab1!" + "密" * 23,
            "is_active": True,
            "is_superuser": False,
            "avatar": None,
            "is_otp": False,
            "permissions": {},
            "settings": {},
        }


class _CurrentUser:
    """提供用户接口长度校验前需要的最小查询契约。"""

    @staticmethod
    async def async_get_by_name(name):
        """模拟用户名尚未被使用。"""
        assert name == "new-user"
        return None


def test_create_user_returns_business_error_for_password_over_72_bytes():
    """新增用户遇到超长密码时应返回可读业务错误。"""
    response = asyncio.run(
        user_endpoint.create_user(
            service=SimpleNamespace(get_by_name=_CurrentUser.async_get_by_name),
            user_in=_CreateUserInput(),
            current_user=_CurrentUser(),
        )
    )

    assert response.success is False
    assert response.message == "密码 UTF-8 编码后不能超过 72 字节"


def test_update_user_returns_business_error_for_password_over_72_bytes():
    """修改用户遇到超长密码时应返回可读业务错误。"""
    user_in = SimpleNamespace(
        model_dump=lambda: {
            "id": 1,
            "name": "user",
            "password": "Ab1!" + "密" * 23,
        }
    )

    response = asyncio.run(
        user_endpoint.update_user(
            service=SimpleNamespace(),
            user_in=user_in,
            current_user=SimpleNamespace(),
        )
    )

    assert response.success is False
    assert response.message == "密码 UTF-8 编码后不能超过 72 字节"
