"""
control_db_guard.py — ANU v2.13 control DB write guard (순수 함수, stdlib only)

chair/anu-control/independent_verifier 는 허용.
그 외 actor의 control 테이블 직접 쓰기는 CONTROL_DB_DIRECT_WRITE_BLOCKED.
코드는 검사만 수행하며 DB 접근 0건.
"""

import re

# ---------------------------------------------------------------------------
# Chair-class actors (허용)
# ---------------------------------------------------------------------------

CHAIR_ACTORS = frozenset([
    "chair",
    "anu-control",
    "anu_control",
    "independent_verifier",
])

# actor 매칭은 소문자 비교 — 매 호출마다 set 을 재생성하지 않도록 1회 사전계산.
_CHAIR_ACTORS_LOWER = frozenset(a.lower() for a in CHAIR_ACTORS)

# ---------------------------------------------------------------------------
# 검출 패턴
# ---------------------------------------------------------------------------

# (pattern_func, description)  — 각 규칙은 (sql_or_op, haystack_lower) 를 받음
def _is_sqlite_write(haystack):
    """sqlite + write/DDL 키워드. INSERT/UPDATE/DELETE 외에 REPLACE 및
    CREATE/ALTER/DROP/TRUNCATE(DDL) 를 대소문자 무관하게 탐지한다 (HIGH 4).
    'INSERT OR REPLACE' / 'DROP TABLE' 등 우회를 차단.

    haystack 는 check_db_write 에서 이미 소문자화되어 전달되므로 재소문자화하지
    않는다 (redundant lower 제거)."""
    if "sqlite" not in haystack:
        return False
    # haystack 는 이미 소문자이므로 소문자 패턴으로 충분하다. 대소문자 무시 플래그는
    # redundant 하여 제거 (MEDIUM). 대문자 SQL 도 호출부에서 lower() 되므로 여전히 탐지된다.
    write_kws = re.search(
        r"\b(insert|update|delete|write|replace|create|alter|drop|truncate)\b",
        haystack,
    )
    return bool(write_kws)


def _is_heartbeat_update(haystack):
    # haystack 이미 소문자 → 소문자 패턴 + 대소문자 무시 플래그 제거 (redundant, MEDIUM).
    return bool(
        re.search(r"update\s+bots\b", haystack)
        and "heartbeat_at" in haystack
    )


def _is_promotion_approved(haystack):
    return "promotions" in haystack and "approved" in haystack


def _is_control_table_write(haystack):
    # REPLACE 및 DDL(CREATE/ALTER/DROP/TRUNCATE) 우회 방어 (HIGH 5).
    # haystack 이미 소문자 → 소문자 패턴 + 대소문자 무시 플래그 제거 (redundant, MEDIUM).
    has_write = bool(re.search(
        r"\b(insert|update|delete|replace|create|alter|drop|truncate)\b",
        haystack,
    ))
    has_table = bool(re.search(r"\b(bots|promotions|ledger)\b", haystack))
    return has_write and has_table


_DETECTORS = [
    (_is_sqlite_write,        "sqlite direct write detected"),
    (_is_heartbeat_update,    "UPDATE bots + heartbeat_at detected"),
    (_is_promotion_approved,  "promotions + APPROVED detected"),
    (_is_control_table_write, "INSERT/UPDATE/DELETE on control table detected"),
]

# ---------------------------------------------------------------------------
# check_db_write
# ---------------------------------------------------------------------------

def check_db_write(actor, sql_or_op):
    """
    Parameters
    ----------
    actor : str   — bot id / actor name
    sql_or_op : str — SQL 문 또는 작업 설명

    Returns
    -------
    dict {"blocked": bool, "verdict": str | None, "reason": str, "actor": str}
    """
    # actor 가 non-string(int 등)이면 (actor or "") 가 그대로 반환되어 .strip()
    # 에서 AttributeError. str() 로 감싸 type guard (MEDIUM).
    actor_lower = str(actor or "").strip().lower()

    # Chair-class 허용
    if actor_lower in _CHAIR_ACTORS_LOWER:
        return {
            "blocked": False,
            "verdict": None,
            "reason":  f"actor={actor!r} is a chair-class actor; write permitted",
            "actor":   actor,
        }

    haystack = (sql_or_op or "").lower()

    for detector, desc in _DETECTORS:
        if detector(haystack):
            return {
                "blocked": True,
                "verdict": "CONTROL_DB_DIRECT_WRITE_BLOCKED",
                "reason":  f"{desc} (actor={actor!r} is not chair-class)",
                "actor":   actor,
            }

    return {
        "blocked": False,
        "verdict": None,
        "reason":  "no control-db write pattern matched",
        "actor":   actor,
    }
