"""
认证模块属性测试（Property-Based Testing）。

使用 hypothesis 验证认证系统的通用正确性属性：
- Property 2: 无效凭据始终被拒绝
- Property 3: 有效 JWT 令牌授权访问
"""

import os

os.environ.setdefault("JWT_SECRET_KEY", "test-secret-key-for-property-tests")

from unittest.mock import MagicMock, patch

from hypothesis import given, settings
from hypothesis import strategies as st

from app.auth.dependencies import CurrentUser, get_current_user
from app.auth.jwt import create_access_token
from app.main import app
from app.routers.auth import router

# 确保路由已挂载
if router not in [r for r in app.routes]:
    app.include_router(router)

from fastapi.testclient import TestClient

client = TestClient(app)


# ---------------------------------------------------------------------------
# 策略（Strategies）
# ---------------------------------------------------------------------------

# 用户名策略：1~64 字符的可打印字符串（排除控制字符）
_username_st = st.text(
    alphabet=st.characters(whitelist_categories=("L", "N", "P", "S")),
    min_size=1,
    max_size=64,
)

# 密码策略：1~128 字符的可打印字符串
_password_st = st.text(
    alphabet=st.characters(whitelist_categories=("L", "N", "P", "S")),
    min_size=1,
    max_size=128,
)

# user_id 策略：正整数
_user_id_st = st.integers(min_value=1, max_value=2**31 - 1)

# site_id 策略：正整数
_site_id_st = st.integers(min_value=1, max_value=2**63 - 1)


def _mock_db_returning(row):
    """构造 mock get_connection，cursor.fetchone() 返回指定行。"""
    mock_conn = MagicMock()
    mock_cursor = MagicMock()
    mock_cursor.fetchone.return_value = row
    mock_conn.cursor.return_value.__enter__ = lambda _: mock_cursor
    mock_conn.cursor.return_value.__exit__ = MagicMock(return_value=False)
    return mock_conn


# ---------------------------------------------------------------------------
# Feature: admin-web-console, Property 2: 无效凭据始终被拒绝
# **Validates: Requirements 1.2**
# ---------------------------------------------------------------------------


@settings(max_examples=100)
@given(username=_username_st, password=_password_st)
@patch("app.routers.auth.get_connection")
def test_invalid_credentials_always_rejected(mock_get_conn, username, password):
    """
    Property 2: 无效凭据始终被拒绝。

    对于任意用户名/密码组合，当数据库中不存在该用户时（fetchone 返回 None），
    登录接口应始终返回 401 状态码。
    """
    # mock 数据库返回 None — 用户不存在
    mock_get_conn.return_value = _mock_db_returning(None)

    resp = client.post(
        "/api/auth/login",
        json={"username": username, "password": password},
    )
    assert resp.status_code == 401, (
        f"期望 401，实际 {resp.status_code}，username={username!r}"
    )


# ---------------------------------------------------------------------------
# Feature: admin-web-console, Property 3: 有效 JWT 令牌授权访问
# **Validates: Requirements 1.3**
# ---------------------------------------------------------------------------

import asyncio

from fastapi.security import HTTPAuthorizationCredentials


def _run_async(coro):
    """在同步上下文中执行异步协程，避免 DeprecationWarning。"""
    loop = asyncio.new_event_loop()
    try:
        return loop.run_until_complete(coro)
    finally:
        loop.close()


@settings(max_examples=100)
@given(user_id=_user_id_st, site_id=_site_id_st)
def test_valid_jwt_grants_access(user_id, site_id):
    """
    Property 3: 有效 JWT 令牌授权访问。

    对于任意 user_id 和 site_id，由系统签发的未过期 access_token
    应能被 get_current_user 依赖成功解析为 CurrentUser 对象，
    且解析出的 user_id 和 site_id 与签发时一致。
    """
    # 生成有效的 access_token
    token = create_access_token(user_id=user_id, site_id=site_id)

    # 直接调用依赖函数验证令牌解析
    credentials = HTTPAuthorizationCredentials(scheme="Bearer", credentials=token)
    result = _run_async(get_current_user(credentials))

    assert isinstance(result, CurrentUser)
    assert result.user_id == user_id, (
        f"user_id 不匹配：期望 {user_id}，实际 {result.user_id}"
    )
    assert result.site_id == site_id, (
        f"site_id 不匹配：期望 {site_id}，实际 {result.site_id}"
    )
