"""Descriptive review-arrival changes around later paid releases; no causal transfer."""
from common import *
g=pd.read_parquet(R/'games.parquet');h=pd.read_csv(R/'historical_game_windows.csv');r=pd.read_parquet(R/'historical_reviews.parquet');coverage=pd.read_csv(R/'history_coverage.csv');complete=set(coverage.loc[coverage.complete_current_paid_catalog,'developer_id']);end=pd.Timestamp('2026-09-01',tz='UTC');start=pd.Timestamp('2014-01-01',tz='UTC')
dates={a:np.sort(x.timestamp_created.to_numpy()) for a,x in r.groupby('appid')}
def count(ids,lo,hi):
    low=lo.timestamp();high=hi.timestamp();return int(sum(np.searchsorted(dates[a],high,side='left')-np.searchsorted(dates[a],low,side='left') for a in ids))
events=[];placebos=[];pergame=[]
for dev in sorted(complete):
    cat=g[g.developer_id.eq(dev)].sort_values(['date','appid'])
    for cur in cat.itertuples():
        t=cur.date
        if t<start or t+pd.Timedelta(days=90)>end:continue
        if not bool(h.set_index('appid').loc[cur.appid,'date_consistent']):continue
        earlier=cat[cat.date.le(t-pd.Timedelta(days=365))&cat.appid.isin(dates)]
        ids=earlier.appid.tolist()
        if not ids:continue
        other_dates=cat.loc[cat.appid.ne(cur.appid),'date']
        isolated=not (other_dates.gt(t-pd.Timedelta(days=180))&other_dates.lt(t+pd.Timedelta(days=180))).any()
        pre=count(ids,t-pd.Timedelta(days=90),t);post=count(ids,t,t+pd.Timedelta(days=90));prev=count(ids,t-pd.Timedelta(days=180),t-pd.Timedelta(days=90))
        events.append(dict(developer_id=dev,credit=cur.credit,new_appid=cur.appid,new_game=cur.name,new_game_url=cur.game_url,developer_url=cur.developer_page_url,date=t,earlier_games=len(ids),pre180_90=prev,pre90=pre,post90=post,difference=post-pre,ratio=(post+.5)/(pre+.5),isolated=isolated,older_appids=';'.join(map(str,ids))))
        for a in ids:
            before=count([a],t-pd.Timedelta(days=90),t);after=count([a],t,t+pd.Timedelta(days=90))
            pergame.append(dict(new_appid=cur.appid,older_appid=a,credit=cur.credit,pre90=before,post90=after,difference=after-before))
        # Same catalog and seasonal period one year earlier. Games must already
        # be at least one year old then, and no catalog release near that date.
        tp=t-pd.DateOffset(years=1);eligible=earlier[earlier.date.le(tp-pd.Timedelta(days=365))].appid.tolist()
        p_isolated=not (cat.date.gt(tp-pd.Timedelta(days=180))&cat.date.lt(tp+pd.Timedelta(days=180))).any()
        if eligible and isolated and p_isolated and tp-pd.Timedelta(days=90)>=start:
            a0=count(eligible,tp-pd.Timedelta(days=90),tp);a1=count(eligible,tp,tp+pd.Timedelta(days=90));b0=count(eligible,t-pd.Timedelta(days=90),t);b1=count(eligible,t,t+pd.Timedelta(days=90))
            placebos.append(dict(new_appid=cur.appid,credit=cur.credit,new_game=cur.name,older_games=len(eligible),placebo_pre=a0,placebo_post=a1,event_pre=b0,event_post=b1,placebo_change=a1-a0,event_change=b1-b0,difference_in_changes=(b1-b0)-(a1-a0),relative_ratio=((b1+.5)/(b0+.5))/((a1+.5)/(a0+.5))))
e=pd.DataFrame(events);e.to_csv(R/'backcatalog_events.csv',index=False);save(placebos,'backcatalog_placebos.csv');save(pergame,'backcatalog_game_details.csv')
rows=[]
for label,a in [('all_events',e),('isolated',e[e.isolated]),('isolated_baseline_ge5',e[e.isolated&e.pre90.ge(5)])]:
    rows.append(dict(group=label,n=len(a),developers=a.developer_id.nunique(),increases=int(a.post90.gt(a.pre90).sum()),increases_ge10=int(a.difference.ge(10).sum()),median_before=a.pre90.median(),median_after=a.post90.median(),median_difference=a.difference.median(),median_ratio=a.ratio.median()))
save(rows,'backcatalog_summary.csv')
print('Backcatalog events',len(e),'isolated',int(e.isolated.sum()),'seasonal placebo pairs',len(placebos))
