#!/usr/bin/env python3
import re
FORBIDDEN={"INSERT","UPDATE","DELETE","MERGE","CALL","COPY","CREATE","ALTER","DROP","TRUNCATE","GRANT","REVOKE","DO","VACUUM","REINDEX","CLUSTER","REFRESH"}
def _mask(sql:str)->str:
    out=[]; i=0; n=len(sql); state='code'; tag=None
    while i<n:
        c=sql[i]; d=sql[i:i+2]
        if state=='code':
            if d=='--': out.extend('  '); i+=2; state='line'; continue
            if d=='/*': out.extend('  '); i+=2; state='block'; continue
            if c=="'": out.append(' '); i+=1; state='single'; continue
            if c=='"': out.append(' '); i+=1; state='double'; continue
            if c=='$':
                m=re.match(r'\$[A-Za-z_][A-Za-z0-9_]*\$|\$\$',sql[i:])
                if m: tag=m.group(0); out.extend(' '*len(tag)); i+=len(tag); state='dollar'; continue
            out.append(c); i+=1; continue
        if state=='line':
            if c=='\n': out.append('\n'); state='code'
            else: out.append(' ')
            i+=1; continue
        if state=='block':
            if d=='*/': out.extend('  '); i+=2; state='code'
            else: out.append('\n' if c=='\n' else ' '); i+=1
            continue
        if state=='single':
            if c=="'":
                if i+1<n and sql[i+1]=="'": out.extend('  '); i+=2
                else: out.append(' '); i+=1; state='code'
            else: out.append('\n' if c=='\n' else ' '); i+=1
            continue
        if state=='double':
            if c=='"':
                if i+1<n and sql[i+1]=='"': out.extend('  '); i+=2
                else: out.append(' '); i+=1; state='code'
            else: out.append('\n' if c=='\n' else ' '); i+=1
            continue
        if state=='dollar':
            if sql.startswith(tag,i): out.extend(' '*len(tag)); i+=len(tag); state='code'; tag=None
            else: out.append('\n' if c=='\n' else ' '); i+=1
    if state in {'single','double','block','dollar'}: raise ValueError('UNTERMINATED_SQL_LITERAL_OR_COMMENT')
    return ''.join(out)
def validate_single_read_only_query(sql:str):
    masked=_mask(sql)
    body=masked.strip()
    if not body: return False,['EMPTY_QUERY']
    # exactly one optional trailing semicolon
    trimmed=body[:-1].rstrip() if body.endswith(';') else body
    issues=[]
    if ';' in trimmed: issues.append('MULTI_STATEMENT_FORBIDDEN')
    tokens=re.findall(r'[A-Za-z_][A-Za-z0-9_]*',trimmed.upper())
    if not tokens or tokens[0] not in {'SELECT','WITH'}: issues.append('QUERY_MUST_START_SELECT_OR_WITH')
    bad=sorted({t for t in tokens if t in FORBIDDEN})
    if bad: issues.append('FORBIDDEN_SQL_TOKEN:'+','.join(bad))
    if 'EXPLAIN' in tokens: issues.append('LEARNER_QUERY_MUST_NOT_EMBED_EXPLAIN')
    return not issues,issues
