"""Read-only statement whitelist for `run_sql`.

In duckdb-mode the MCP process opens the DuckDB file read-write (the daemon
needs to ingest into it), so we can't rely on a read-only connection to
gate writes. Instead, the agent-facing `run_sql` rejects any statement
that isn't a SELECT-shaped read or a session-scoped TEMP DDL.

Whitelist > heuristic: only the verbs listed here are allowed. Everything
else (INSERT/UPDATE/DELETE/ALTER/DROP/ATTACH/CREATE-non-TEMP/...) raises.
"""

import re

_RO_FIRST_TOKEN = re.compile(
    r"\s*(SELECT|WITH|SHOW|PRAGMA|EXPLAIN|DESCRIBE|EXECUTE)\b",
    re.IGNORECASE,
)
_RO_TEMP_DDL = re.compile(
    r"\s*CREATE\s+(?:OR\s+REPLACE\s+)?(?:TEMP|TEMPORARY)\s+(?:TABLE|VIEW|MACRO)\b",
    re.IGNORECASE,
)
_COMMENT_LINE = re.compile(r"--[^\n]*")
_COMMENT_BLOCK = re.compile(r"/\*.*?\*/", re.DOTALL)


def validate_read_only(query: str) -> None:
    """Raise ValueError if `query` contains any non-read statement.

    Splits on `;` (DuckDB lets `run_sql` execute multiple statements
    sequentially), strips SQL comments first so a `-- comment with INSERT`
    won't trip the check, and rejects any statement whose first verb isn't
    in the read-only allowlist.
    """
    stripped = _COMMENT_BLOCK.sub("", _COMMENT_LINE.sub("", query))
    for stmt in stripped.split(";"):
        s = stmt.strip()
        if not s:
            continue
        if _RO_FIRST_TOKEN.match(s) or _RO_TEMP_DDL.match(s):
            continue
        first = s.split(None, 1)[0] if s.split() else "(empty)"
        raise ValueError(
            f"run_sql is read-only: statement starting with {first!r} is not "
            "allowed. Allowed: SELECT, WITH, SHOW, PRAGMA, EXPLAIN, DESCRIBE, "
            "EXECUTE, CREATE [OR REPLACE] TEMP {TABLE|VIEW|MACRO}."
        )
