import json,os,re,subprocess,sys,shutil,hashlib
N=sys.argv[1]
SBX=N+'/sbx'
D='/Users/chudy/Library/Application Support/Kodi/addons/plugin.video.milionar'
base=json.load(open(N+'/base_ts.json'))
release=set(open(N+'/release_files.txt').read().split('\n'))
cmds=json.load(open(N+'/bash.json'))
BAD=('userdata','addon_data','subprocess','os.remove','rmtree','unlink','ssh ','scp ','urllib','requests','socket','Documents/milionar','.claude','os.system','shutil.move','os.rename','Popen')
def snapshot():
    snap={}
    for root,dirs,fs in os.walk(SBX):
        dirs[:]=[d for d in dirs if d not in ('__pycache__','out')]
        for f in fs:
            p=os.path.join(root,f); rel=os.path.relpath(p,SBX)
            if rel in release or not rel.endswith(('.py','.sh','.md','.xml','.txt','.json','.po','.xsp','.tsv')): continue
            try: snap[rel]=open(p,'rb').read()
            except: pass
    return snap
def fix(text):
    return text.replace(D,SBX)
def cwd_at(cmd,pos):
    cur=SBX
    for m in re.finditer(r'(?:^|[;&|]\s*|&&\s*)cd\s+(?:"([^"]+)"|\'([^\']+)\'|([^\s;&|]+))',cmd[:pos]):
        t=m.group(1) or m.group(2) or m.group(3)
        t=fix(t)
        if t.startswith('~') or t.startswith('$'): cur=SBX; continue
        cur=t if t.startswith('/') else os.path.normpath(os.path.join(cur,t))
        if not cur.startswith(SBX): cur=SBX+'/__outside__'
    return cur
def heredocs(cmd):
    out=[]
    for m in re.finditer(r"python3?(?: -[A-Za-z]*)?(?: -)?(?: \"?\$?\w+\"?)* ?<<-?['\"]?(\w+)['\"]?\n(.*?)\n\1(?:\s|$)",cmd,re.S):
        out.append(('py',(m.group(2),cwd_at(cmd,m.start()))))
    for m in re.finditer(r"cat >>? ?(\"?[^\s\"<]+\"?) <<-?['\"]?(\w+)['\"]?\n(.*?)\n\2(?:\s|$)",cmd,re.S):
        out.append(('cat',(m.group(1).strip('"'),m.group(3),'>>' in cmd[m.start():m.start()+8],cwd_at(cmd,m.start()))))
    return out
def seds(cmd):
    return [l.strip() for l in cmd.split('\n') if re.match(r"\s*(?:\w+ &&\s*)?sed -i",l)]
stats={'py_ok':0,'py_fail':0,'cat':0,'sed':0,'skipped':0}
fails=[]
START=int(sys.argv[2]) if len(sys.argv)>2 else 0
for c in cmds[START:]:
    cmd=c['cmd']; ts=c['ts']
    if 'plugin.video.milionar' not in cmd and 'scripts/' not in cmd and 'wcs/' not in cmd and 'resources/' not in cmd: continue
    items=heredocs(cmd)
    sl=[]
    for l in seds(cmd):
        sl.append(re.sub(r'^.*?&&\s*','',l) if '&&' in l.split('sed',1)[0] else l)
    if not items and not sl: continue
    before=snapshot()
    for kind,body in items:
        if kind=='cat':
            path,content,append,cwd=body
            path=fix(path)
            if not path.startswith('/'): path=os.path.join(cwd,path)
            if not path.startswith(SBX): stats['skipped']+=1; continue
            os.makedirs(os.path.dirname(path),exist_ok=True)
            open(path,'a' if append else 'w').write(content+'\n'); stats['cat']+=1
        else:
            body,cwd=body
            body2=fix(body)
            if any(b in body2 for b in BAD) or '__outside__' in cwd: stats['skipped']+=1; continue
            try:
                r=subprocess.run(['python3','-'],input=body2,text=True,capture_output=True,cwd=cwd,timeout=60)
                if r.returncode==0: stats['py_ok']+=1
                else: stats['py_fail']+=1; fails.append((ts,c['sess'],r.stderr.strip().split('\n')[-1][:160]))
            except Exception as e: stats['py_fail']+=1; fails.append((ts,c['sess'],str(e)[:100]))
    for l in sl:
        for part in l.split(';'):
            part=part.strip()
            if not part.startswith('sed -i') or any(b in part for b in BAD) or 'kodi_live' in part: continue
            try:
                subprocess.run(['bash','-c',fix(part)],cwd=SBX,capture_output=True,text=True,timeout=20); stats['sed']+=1
            except Exception: pass
    after=snapshot()
    for rel in set(before)|set(after):
        b,a=before.get(rel),after.get(rel)
        if b==a: continue
        p=os.path.join(SBX,rel)
        t=base.get(rel,'')
        if b is not None and ts<=t:      # a newer structured snapshot exists: this patch is already inside it
            open(p,'wb').write(b)
        elif b is None and a is not None and ts<=t:
            pass
    # runtime files edited: revert from real dir
    for rel in release:
        rp=os.path.join(SBX,rel); dp=os.path.join(D,rel)
        try:
            if os.path.exists(dp) and open(rp,'rb').read()!=open(dp,'rb').read(): shutil.copy2(dp,rp)
        except Exception: pass
print(stats)
for f in fails[:60]: print(f)
