import sys, asyncio, json, re, time, random, os
sys.path.insert(0,'/opt/data/scripts')
import cbm_cdp
LOG='/opt/data/agent-state/memory/ig-encourage-agent-log.json'
WARN_RE=re.compile(r'try again later|captcha|suspicious|checkpoint|temporarily blocked|restrict certain activity|automated behavior|confirm it|unusual|action block|we limit how often',re.I)
VIN_RE=re.compile(r'vincent\.emmanuel\.lee|Vincent Emmanuel Lee',re.I)
ITEMS=[
 {'url':'https://www.instagram.com/craiggroeschel/reel/DZtPb4YjO6m/','postId':'DZtPb4YjO6m','account':'craiggroeschel','source':'profile:craiggroeschel','fit':'Forgiveness teaching grounded in grace and Ephesians 4:32.','comment':'That line lands with so much grace: we forgive because we have been forgiven first. Ephesians 4:32 comes to mind. Jesus gives us what we need to release what we could not carry.','distinct':'we forgive because we have been forgiven first'},
 {'url':'https://www.instagram.com/craiggroeschel/p/DZr6wjyDmRL/','postId':'DZr6wjyDmRL','account':'craiggroeschel','source':'profile:craiggroeschel','fit':'Encouragement about trusting God with pain and purpose.','comment':'This is a steady reminder for painful seasons: trusting God does not mean we understand every chapter. It means we are held by a Father who wastes nothing.','distinct':'we are held by a Father who wastes nothing'},
 {'url':'https://www.instagram.com/craiggroeschel/reel/DZqoW6Fjpti/','postId':'DZqoW6Fjpti','account':'craiggroeschel','source':'profile:craiggroeschel','fit':'Practical discipleship encouragement about small faithful disciplines.','comment':'Small obedience rarely feels dramatic, but it forms the heart over time. I love the reminder that faithfulness today is one quiet yes at a time.','distinct':'faithfulness today is one quiet yes at a time'},
 {'url':'https://www.instagram.com/craiggroeschel/reel/DZlexJOjeXV/','postId':'DZlexJOjeXV','account':'craiggroeschel','source':'profile:craiggroeschel','fit':'Prayer encouragement centered on the Spirit interceding and the Father hearing.','comment':'Such a comforting truth. Even when words feel thin, the Spirit intercedes and the Father hears. Prayer is never a lonely room.','distinct':'Prayer is never a lonely room'},
 {'url':'https://www.instagram.com/life.church/reel/DZtUAx5Dn7l/','postId':'DZtUAx5Dn7l','account':'life.church','source':'profile:life.church','fit':'Encourages trust in God moving during waiting seasons.','comment':'Waiting can feel so hidden, but this is a beautiful reminder that the Father is still present and working there. He meets us in the slow places too.','distinct':'He meets us in the slow places too'},
 {'url':'https://www.instagram.com/life.church/p/DZsdEPJH4f3/','postId':'DZsdEPJH4f3','account':'life.church','source':'profile:life.church','fit':'Affirms vocation/family as places for mission and faithfulness.','comment':'This is a needed reframe: family and work are not interruptions to mission. They can be places where love, service, and faithfulness become visible.','distinct':'family and work are not interruptions to mission'},
 {'url':'https://www.instagram.com/wellwateredwomen/p/DYdKybplqbi/','postId':'DYdKybplqbi','account':'wellwateredwomen','source':'profile:wellwateredwomen','fit':'Grace-based encouragement for those fearing they are the exception to God’s faithfulness.','comment':'This speaks tenderly to the fear of being the exception. Philippians 1:6 is such steady ground: the Father does not abandon the work He began.','distinct':'the Father does not abandon the work He began'},
 {'url':'https://www.instagram.com/youversion/p/DZrLO0aDnOh/','postId':'DZrLO0aDnOh','account':'youversion','source':'profile:youversion','fit':'Direct reflection on John 15:12 and Christlike love.','comment':'John 15:12 keeps love from becoming vague. Jesus gives love a shape: receive His love, then embody it with patience, truth, and mercy toward the people in front of us.','distinct':'Jesus gives love a shape'},
]

def load_log():
    try:
        with open(LOG) as f: return json.load(f)
    except Exception: return {'comments': [], 'errors': []}

def save_log(log):
    os.makedirs(os.path.dirname(LOG), exist_ok=True)
    tmp=LOG+'.tmp'
    with open(tmp,'w') as f: json.dump(log,f,indent=2,ensure_ascii=False)
    os.replace(tmp,LOG)

def done_ids():
    log=load_log(); return {c.get('postId') for c in log.get('comments',[]) if c.get('verified') and c.get('postId')}
async def page_warning(b):
    body=await b.eval('document.body.innerText') or ''
    return bool(WARN_RE.search(body)), body
async def find_textarea(b):
    # scroll/click; works for p and many reels
    for dy in [250,500,-300,800,0]:
        if dy: await b.eval(f'window.scrollBy(0,{dy})')
        await asyncio.sleep(1)
        res=await b.eval("""(() => {
          const sels=['textarea[aria-label*=\"comment\" i]','textarea'];
          for (const sel of sels){ const ta=document.querySelector(sel); if(ta){ ta.scrollIntoView({behavior:'instant',block:'center'}); ta.click(); ta.focus(); return {found:true, tag:'textarea', value:ta.value||''}; }}
          const btns=[...document.querySelectorAll('[role="button"], button')];
          for (const b of btns){ const t=(b.textContent||'').trim().toLowerCase(); const a=(b.getAttribute('aria-label')||'').toLowerCase(); if(a.includes('comment') || t==='comment'){ b.scrollIntoView({behavior:'instant',block:'center'}); b.click(); return {found:false, clickedComment:true}; }}
          return {found:false};
        })()""")
        if res and res.get('found'):
            return True
        await asyncio.sleep(2)
        res2=await b.eval("""(() => { const ta=document.querySelector('textarea[aria-label*=\"comment\" i], textarea'); if(!ta) return false; ta.scrollIntoView({behavior:'instant',block:'center'}); ta.click(); ta.focus(); return true; })()""")
        if res2: return True
    return False
async def post_one(b,item,started):
    await b.goto(item['url'],6)
    warn,body=await page_warning(b)
    if warn: return {'status':'warning','url':item['url'],'step':'precheck'}
    if VIN_RE.search(body): return {'status':'skip','reason':'visible Vincent comment already present', **item}
    ok=await find_textarea(b)
    if not ok: return {'status':'failed','reason':'comment textarea not found', **item}
    await asyncio.sleep(1)
    await b.send('Input.insertText', {'text': item['comment']})
    await asyncio.sleep(random.uniform(1.5,2.8))
    clicked=await b.eval("""(() => {
      const candidates=[...document.querySelectorAll('[role="button"], button')];
      for (const btn of candidates){ if((btn.textContent||'').trim()==='Post'){ btn.scrollIntoView({behavior:'instant',block:'center'}); btn.click(); return true; }}
      return false;
    })()""")
    if not clicked: return {'status':'failed','reason':'Post button not found after typing', **item}
    await asyncio.sleep(7)
    warn2,body2=await page_warning(b)
    if warn2: return {'status':'warning','url':item['url'],'step':'after_post'}
    verified=bool(VIN_RE.search(body2) and item['distinct'] in body2)
    shot=f"/tmp/ig-cbm-{item['postId']}-result.png"
    try: await b.screenshot(shot)
    except Exception as e: shot=''; print('SCREENSHOT_ERROR',item['postId'],repr(e),flush=True)
    if not verified:
        return {'status':'failed','reason':'DOM verification failed','hasVincent':bool(VIN_RE.search(body2)),'hasDistinct':item['distinct'] in body2, **item}
    log=load_log()
    log.setdefault('comments',[]).append({'timestamp':time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),'startedAt':started,'source':'cbm-agent-cron','postId':item['postId'],'url':item['url'],'comment':item['comment'],'account':item['account'],'sourceAccount':item['source'],'reason':item['fit'],'verified':True,'screenshot':shot,'verificationMethod':'CBM CDP DOM text match'})
    save_log(log)
    return {'status':'posted','verified':True,'screenshot':shot, **item}
async def main():
    started=time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
    cbm_cdp.launch_profile(); b=await cbm_cdp.connect(True)
    st=await cbm_cdp.page_state(b)
    if st.get('hasLogin') or st.get('warning'):
        print(json.dumps({'stopped':'login_or_warning','state':{k:st.get(k) for k in ['url','hasLogin','warning']}})); return
    posted=[]; skipped=[]; failed=[]
    for idx,item in enumerate(ITEMS):
        if item['postId'] in done_ids():
            skipped.append({'url':item['url'],'postId':item['postId'],'reason':'local log duplicate'}); continue
        if posted:
            pause=random.randint(22,39)
            print('PAUSE',pause,'seconds',flush=True); await asyncio.sleep(pause)
        print('POST_ATTEMPT',item['postId'],item['url'],flush=True)
        res=await post_one(b,item,started)
        print('RESULT',json.dumps({k:res.get(k) for k in ['status','postId','reason','verified','hasVincent','hasDistinct','step']},ensure_ascii=False),flush=True)
        if res['status']=='posted': posted.append(res)
        elif res['status']=='skip': skipped.append(res)
        elif res['status']=='warning': failed.append(res); break
        else:
            failed.append(res)
            # On a single textarea issue for reels, continue to non-identical candidates; on verification failure stop for safety.
            if res.get('reason') == 'DOM verification failed': break
        if len(posted)>=8: break
    print('SUMMARY_JSON_START')
    print(json.dumps({'startedAt':started,'posted':posted,'skipped':skipped,'failed':failed,'postedCount':len(posted)},ensure_ascii=False,indent=2))
    print('SUMMARY_JSON_END')
asyncio.run(main())
