"""Exact owned metadata keys only; no FLUSHALL, wildcard deletion or Lua privileges."""
import hashlib
import os
import re

from data_safety import digest


def client():
    import redis
    return redis.Redis.from_url(os.environ['CONTEXTRA_METADATA_REDIS_URL'], socket_timeout=3, socket_connect_timeout=3)


def namespace():
    n = os.environ.get('CONTEXTRA_METADATA_NAMESPACE','contextra:metadata:v1')
    if not re.fullmatch('[A-Za-z0-9:_-]{1,80}',n): raise ValueError('REDIS_NAMESPACE')
    return n


def stamp(raw):
    return hashlib.sha256(raw).hexdigest() if raw is not None else None


def plan(cameras, filters):
    known = {hashlib.sha256(str(r.get('camera_id',r.get('source_id'))).encode()).hexdigest():
             str(r.get('camera_id',r.get('source_id'))) for r in cameras}
    root = namespace(); pattern=re.compile(re.escape(root)+r':camera:([0-9a-f]{64}):(frames|status)$')
    r=client()
    try:
        ids=set()
        for key in r.scan_iter(match=root+':camera:*',count=100):
            m=pattern.fullmatch(key.decode())
            if m:ids.add(m[1])
            if len(ids)>10000:raise ValueError('REDIS_BATCH_LIMIT')
        result=[]
        for ident in sorted(ids):
            camera=known.get(ident,'unknown')
            if filters.get('camera') and filters['camera']!=camera:continue
            keys=[root+':camera:'+ident+':'+suffix for suffix in ('frames','status')]
            dumps=[r.dump(k) for k in keys]
            result.append(dict(kind='redis',id=ident,camera=camera,date='',path=root+':camera:'+ident,
                bytes=sum(len(v or b'') for v in dumps),stamp=[stamp(v) for v in dumps],blocked=None))
        return result
    finally:r.close()


def apply(item, started):
    root=namespace()
    if not re.fullmatch('[0-9a-f]{64}',item['id']) or item['path']!=root+':camera:'+item['id']:
        raise ValueError('REDIS_OWNERSHIP')
    r=client();keys=[item['path']+':'+s for s in ('frames','status')]
    try:
        with r.pipeline() as p:
            p.watch(*keys,root+':channels')
            current=[stamp(p.dump(k)) for k in keys]
            if any(c is not None and c!=old for c,old in zip(current,item['stamp'])):
                raise ValueError('REDIS_CHANGED')
            mapped=p.hget(root+':channels',item['id'])
            if mapped not in (None,keys[0].encode()):raise ValueError('REDIS_CHANNEL_MAPPING')
            p.multi();p.delete(*keys);p.hdel(root+':channels',item['id']);p.execute()
    finally:r.close()
