import fcntl
import hashlib
import json
import os
from pathlib import Path
import time

from process_ops import R, p

path=Path('/workspace/contextra-runtime-config/metadata-active/users.acl')
query=p.base.m.query
assert p.read('approval.json')['publisher_commands']==['get','watch','unwatch']
assert not (R/'acl-start.json').exists()
before=path.read_bytes()
writer=query('admin','ACL','GETUSER','contextra_publisher')
reader=query('admin','ACL','GETUSER','contextra_reader')
assert not any('+'+c in writer['commands'].split() for c in ('get','watch','unwatch'))
(R/'users.acl.before').write_bytes(before)
p.save('acl-start.json',dict(at=time.time(),sha=hashlib.sha256(before).hexdigest(),writer=writer,reader=reader))
lines=before.decode().splitlines(keepends=True)
indices=[i for i,line in enumerate(lines) if line.startswith('user contextra_publisher ')]
assert len(indices)==1
lines[indices[0]]=lines[indices[0]].rstrip()+' +get +watch +unwatch\n'
after=''.join(lines).encode()
with path.open('r+b') as handle:
    fcntl.flock(handle,fcntl.LOCK_EX)
    assert handle.read()==before
    try:
        assert query('admin','ACL','SETUSER','contextra_publisher','+get','+watch','+unwatch')=='OK'
        # Preserve the bind-mounted inode so both restart and recreate see the same ACL file.
        handle.seek(0);handle.write(after);handle.truncate();handle.flush();os.fsync(handle.fileno())
        current=query('admin','ACL','GETUSER','contextra_publisher')
        assert set(current['commands'].split())-set(writer['commands'].split())=={'+get','+watch','+unwatch'}
        assert all(current[k]==writer[k] for k in ('passwords','keys','channels','flags','selectors'))
        assert query('admin','ACL','GETUSER','contextra_reader')==reader
        for cmd in (['GET','contextra:metadata:v1:permission-probe'],['WATCH','contextra:metadata:v1:permission-probe'],['UNWATCH']):
            assert query('admin','ACL','DRYRUN','contextra_publisher',*cmd)=='OK'
        denied=[]
        for cmd in (['GET','outside:probe'],['WATCH','outside:probe'],['EVAL','return 1','0'],['CONFIG','GET','maxmemory']):
            try: result=query('admin','ACL','DRYRUN','contextra_publisher',*cmd)
            except RuntimeError as error:result=str(error)
            assert result!='OK';denied.append(cmd[0])
    except Exception:
        query('admin','ACL','SETUSER','contextra_publisher','-get','-watch','-unwatch')
        handle.seek(0);handle.write(before);handle.truncate();handle.flush();os.fsync(handle.fileno())
        raise
assert path.read_bytes()==after
p.save('acl-result.json',dict(at=time.time(),commands=['GET','WATCH','UNWATCH'],reader_unchanged=True,
    keys_auth_unchanged=True,denied=denied,persistent_sha=hashlib.sha256(after).hexdigest(),
    inode_preserved=True,redis_restart=False))
print('ACL PASS: writer GET/WATCH/UNWATCH only; reader/auth/key scope preserved; no Redis restart')
