"""Shared bounded, serial collector. No account or individual reviewer data saved."""
from pathlib import Path
import requests,json,time,datetime as dt,gzip,hashlib,fcntl
R=Path(__file__).resolve().parent
def utc():return dt.datetime.now(dt.timezone.utc).isoformat()
def dump(p,d):
    tmp=p.with_suffix(p.suffix+'.tmp');tmp.write_text(json.dumps(d,indent=2,ensure_ascii=False));tmp.replace(p)
class Client:
    def __init__(self):
        self.lock=(R/'collection.lock').open('w');fcntl.flock(self.lock,fcntl.LOCK_EX|fcntl.LOCK_NB)
        self.session=requests.Session();self.session.headers['User-Agent']='SteamCatalogResearch/0.3 (bounded public language-summary study)'
    def get(self,url,params,kind,key):
        policy=json.loads((R/'collection_policy.json').read_text())
        if not policy['active']:raise RuntimeError('Scoped collection inactive')
        if (R/'STOP').exists():raise RuntimeError('STOP file present')
        log=R/'requests.jsonl';lines=log.read_text().splitlines() if log.exists() else []
        count=len(lines)+4
        if count>=policy['max_requests_including_probes']:raise RuntimeError('Request cap reached')
        if lines:
            last=json.loads(lines[-1]);delay=policy['min_interval_seconds']-(time.time()-last['started_epoch'])
            if delay>0:time.sleep(delay)
        row={'sequence':count+1,'started_at':utc(),'started_epoch':time.time(),'url':url,'params':params,'kind':kind,'key':key}
        try:
            r=self.session.get(url,params=params,timeout=(10,35),stream=True)
            row['status']=r.status_code;row['final_url']=r.url
            r.raise_for_status();chunks=[];size=0
            for chunk in r.iter_content(65536):
                size+=len(chunk)
                if size>policy['max_response_bytes']:raise RuntimeError('Response too large')
                chunks.append(chunk)
            body=b''.join(chunks);row['bytes']=len(body);row['sha256']=hashlib.sha256(body).hexdigest();return body
        except Exception as e:
            row['error']=str(e);policy['active']=False;policy['stop_reason']=str(e);dump(R/'collection_policy.json',policy);raise
        finally:
            row['finished_at']=utc()
            with log.open('a') as f:f.write(json.dumps(row,ensure_ascii=False)+'\n')
    def summary(self,appid,language):
        folder=R/'api_summaries';folder.mkdir(exist_ok=True);dest=folder/f'{appid}_{language}.json'
        if dest.exists():return json.loads(dest.read_text())['query_summary']
        params={'json':1,'filter':'recent','language':language,'purchase_type':'steam','review_type':'all','num_per_page':1,'filter_offtopic_activity':1}
        body=self.get(f'https://store.steampowered.com/appreviews/{appid}',params,'review_summary',f'{appid}_{language}')
        data=json.loads(body)
        if data.get('success')!=1 or 'query_summary' not in data:raise ValueError('Invalid review summary')
        q=data['query_summary'];assert q['total_positive']+q['total_negative']==q['total_reviews']
        dump(dest,{'appid':appid,'language':language,'retrieved_at':utc(),'params':params,'query_summary':q})
        return q
