"""python -m tools.region_flow_shadow --input events.jsonl --output DIR [--follow]."""
import argparse
import json
from pathlib import Path
import resource
import signal
import time
from .core import FlowShadow, EntryCatalog


def main():
    p=argparse.ArgumentParser(description=__doc__)
    p.add_argument('--input',type=Path,required=True);p.add_argument('--output',type=Path,required=True)
    p.add_argument('--run-id',required=True);p.add_argument('--entries',type=Path);p.add_argument('--allow-oracle',action='store_true')
    p.add_argument('--interval',type=int,choices=[100,250,500],default=250)
    p.add_argument('--checkpoints',default='',help='confirmed execution counts, comma separated')
    p.add_argument('--follow',action='store_true');p.add_argument('--idle-timeout',type=float,default=0)
    args=p.parse_args(); args.output.mkdir(parents=True,exist_ok=True)
    if (args.output/'index.json').exists() or list(args.output.glob('S*.json')):
        raise SystemExit('Refuse to overwrite a previous run; use a new output directory')
    catalog=EntryCatalog(json.loads(args.entries.read_text()) if args.entries else [],args.allow_oracle)
    shadow=FlowShadow(args.run_id,catalog); cuts={int(x) for x in args.checkpoints.split(',') if x}
    index=[]; stopping=[False]; last_saved=0; started=time.perf_counter(); errors=[]; max_buffer=0
    def stop(*_):stopping[0]=True
    signal.signal(signal.SIGTERM,stop); signal.signal(signal.SIGINT,stop)
    def snapshot(reasons):
        nonlocal last_saved
        try:
            s=shadow.snapshot(reasons);data=json.dumps(s,ensure_ascii=False,separators=(',',':'))+'\n'
            f=args.output/(s['id']+'.json');tmp=f.with_suffix('.tmp');tmp.write_text(data);tmp.replace(f)
            index.append(dict(id=s['id'],max_event_seq=s['max_event_seq'],confirmed_count=s['confirmed_count'],
                communities=len(s['communities']),infomap_ms=s['infomap_ms'],total_compute_ms=s['total_compute_ms'],bytes=len(data.encode()),reasons=reasons))
            last_saved=len(shadow.events)
            tmp=args.output/'index.tmp';tmp.write_text(json.dumps(dict(run_id=args.run_id,interval=args.interval,snapshots=index),ensure_ascii=False));tmp.replace(args.output/'index.json')
        except Exception as e:
            errors.append(dict(max_event_seq=shadow.max_seq,error=repr(e),stage='snapshot'))
    # Binary tail retains partial lines, detects truncation/rotation; does not drop backlog.
    with args.input.open('rb') as f:
        buffer=b''; idle=time.monotonic()
        while not stopping[0]:
            pos=f.tell();line=f.readline()
            if line:
                buffer+=line;max_buffer=max(max_buffer,len(buffer));idle=time.monotonic()
                if not buffer.endswith(b'\n'):
                    if not args.follow:
                        raise ValueError('incomplete final event line')
                    continue
                row=json.loads(buffer);buffer=b''
                accepted=shadow.consume(row)
                if accepted:
                    n=len(shadow.events);reasons=[]
                    if n%args.interval==0:reasons.append('periodic')
                    if n in cuts:reasons.append('checkpoint')
                    if reasons:snapshot(reasons)
                continue
            if not args.follow:break
            if args.input.stat().st_size<f.tell():raise RuntimeError('event file truncated; use distinct run and output')
            if args.idle_timeout and time.monotonic()-idle>=args.idle_timeout:break
            time.sleep(.05)
    if buffer:errors.append(dict(stage='tail',error='incomplete event line, not consumed'))
    if len(shadow.events)!=last_saved:snapshot(['terminal_truncated'])
    perf=dict(run_id=args.run_id,interval=args.interval,mode='follow' if args.follow else 'replay',
        accepted=len(shadow.events),max_event_seq=shadow.max_seq,wall_seconds=time.perf_counter()-started,
        process_peak_rss_kib=resource.getrusage(resource.RUSAGE_SELF).ru_maxrss,
        pending_execution_count=len(shadow.events)-last_saved,max_line_buffer_bytes=max_buffer,
        snapshot_count=len(index),errors=errors,diagnostics=dict(shadow.diagnostics),
        native_throughput_impact='NOT_MEASURED_NO_DEVICE_RUN',backlog='file retained; no producer blocking; no live capture timestamp alignment benchmark')
    (args.output/'performance.json').write_text(json.dumps(perf,indent=2));print(json.dumps(perf))
    if errors:raise SystemExit(1)

if __name__=='__main__':main()
