# -*- coding: utf-8 -*-
"""数据库查看器属性测试（Property-Based Testing）。

使用 hypothesis 验证数据库查看器的通用正确性属性：
- Property 17: SQL 写操作拦截
- Property 18: SQL 查询结果行数限制

测试策略：
- Property 17: 生成包含写操作关键词（随机大小写混合）的 SQL 字符串，
  验证 _WRITE_KEYWORDS 正则表达式能匹配到
- Property 18: 生成随机长度的行列表（可能超过 1000 行），
  验证截取前 _MAX_ROWS 个元素后长度 <= 1000
"""

import os

os.environ.setdefault("JWT_SECRET_KEY", "test-secret-key-for-db-viewer-properties")

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

from app.routers.db_viewer import _WRITE_KEYWORDS, _MAX_ROWS


# ---------------------------------------------------------------------------
# 通用策略（Strategies）
# ---------------------------------------------------------------------------

# 写操作关键词列表
_WRITE_OPS = ["INSERT", "UPDATE", "DELETE", "DROP", "TRUNCATE"]

# SQL 前缀/后缀：不含写操作关键词的简单文本
_sql_filler_st = st.text(
    alphabet=st.characters(
        whitelist_categories=("L", "N", "S"),
        blacklist_characters="\x00",
    ),
    min_size=0,
    max_size=50,
)

# 随机大小写混合的写操作关键词
_random_case_keyword_st = st.sampled_from(_WRITE_OPS).flatmap(
    lambda kw: st.tuples(
        st.just(kw),
        st.lists(
            st.booleans(),
            min_size=len(kw),
            max_size=len(kw),
        ),
    ).map(
        lambda pair: "".join(
            c.upper() if flag else c.lower()
            for c, flag in zip(pair[0], pair[1])
        )
    )
)


# ---------------------------------------------------------------------------
# Feature: admin-web-console, Property 17: SQL 写操作拦截
# **Validates: Requirements 7.5**
# ---------------------------------------------------------------------------

@settings(max_examples=200)
@given(
    prefix=_sql_filler_st,
    keyword=_random_case_keyword_st,
    suffix=_sql_filler_st,
)
def test_write_keywords_always_detected(prefix, keyword, suffix):
    """Property 17: SQL 写操作拦截。

    包含 INSERT、UPDATE、DELETE、DROP、TRUNCATE 关键词（不区分大小写）的
    SQL 语句，_WRITE_KEYWORDS 正则表达式应能匹配到。

    策略：在随机前缀和后缀之间插入一个随机大小写混合的写操作关键词，
    用空格分隔以确保 \\b 词边界能匹配。
    """
    # 用空格分隔确保词边界匹配
    sql = f"{prefix} {keyword} {suffix}"

    match = _WRITE_KEYWORDS.search(sql)
    assert match is not None, (
        f"正则表达式未能匹配到写操作关键词：sql={sql!r}, keyword={keyword!r}"
    )
    # 匹配到的关键词（转大写后）应在写操作列表中
    assert match.group(1).upper() in _WRITE_OPS, (
        f"匹配到的关键词 '{match.group(1)}' 不在写操作列表中"
    )


# ---------------------------------------------------------------------------
# Feature: admin-web-console, Property 18: SQL 查询结果行数限制
# **Validates: Requirements 7.4**
# ---------------------------------------------------------------------------

# 模拟数据库返回的行：每行是一个简单列表
_row_st = st.lists(
    st.one_of(st.integers(), st.text(max_size=20), st.none()),
    min_size=1,
    max_size=5,
)

# 行列表策略：0 到 3000 行（覆盖超过 _MAX_ROWS 的情况）
_rows_st = st.lists(_row_st, min_size=0, max_size=3000)


@settings(max_examples=200)
@given(rows=_rows_st)
def test_row_count_never_exceeds_max(rows):
    """Property 18: SQL 查询结果行数限制。

    对任意长度的行列表，取前 _MAX_ROWS 个元素后，
    结果长度应 <= 1000。

    这等价于 cur.fetchmany(_MAX_ROWS) 的行为：
    数据库游标最多返回 _MAX_ROWS 行。
    """
    # 模拟 fetchmany(_MAX_ROWS) 的行为
    truncated = rows[:_MAX_ROWS]

    assert len(truncated) <= _MAX_ROWS, (
        f"截取后行数 {len(truncated)} 超过上限 {_MAX_ROWS}"
    )

    # 额外验证：如果原始行数 <= _MAX_ROWS，截取后应保留全部
    if len(rows) <= _MAX_ROWS:
        assert len(truncated) == len(rows), (
            f"原始行数 {len(rows)} <= {_MAX_ROWS}，截取后应保留全部，"
            f"实际 {len(truncated)}"
        )

    # 额外验证：如果原始行数 > _MAX_ROWS，截取后应恰好为 _MAX_ROWS
    if len(rows) > _MAX_ROWS:
        assert len(truncated) == _MAX_ROWS, (
            f"原始行数 {len(rows)} > {_MAX_ROWS}，截取后应恰好为 {_MAX_ROWS}，"
            f"实际 {len(truncated)}"
        )
