
import asyncio, sys, re, json, os, time, random
from pathlib import Path
sys.path.insert(0,'/opt/data/scripts')
import cbm_cdp

LOG='/opt/data/agent-state/memory/ig-encourage-agent-log.json'
START=time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
WARNING_RE=re.compile(r'(try again later|suspicious activity|captcha|checkpoint|challenge_required|login challenge|temporarily blocked|we restrict certain activity|action blocked|confirm it\'s you|help us confirm)', re.I)
TARGETS=[
 {"postId":"DZlUcBCiWpm","url":"https://www.instagram.com/p/DZlUcBCiWpm/","account":"youversion","sourceDetail":"home/profile:youversion","reason":"Waiting-season encouragement that points to God’s nearness and hope.","comment":"This is a timely reminder. Waiting can feel silent, but the Father is still near and faithful. Romans 15:13 comes to mind: hope by the power of the Holy Spirit.","needle":"Waiting can feel silent"},
 {"postId":"DYkqqTVlSSm","url":"https://www.instagram.com/crosswaybooks/p/DYkqqTVlSSm/","account":"crosswaybooks","sourceDetail":"profile:crosswaybooks","reason":"Simple theological quote about image-bearing, calling, and God’s glory.","comment":"Beautifully said. Genesis 1 dignity and calling together: known by God, reflecting Him, and living for His glory right where He has placed us.","needle":"Genesis 1 dignity"},
 {"postId":"DZp6s5yDpin","url":"https://www.instagram.com/youversion/p/DZp6s5yDpin/","account":"youversion","sourceDetail":"profile:youversion","reason":"Call to share faith with gentleness and love.","comment":"This is a strong call. Sharing faith with steady love matters because people are not projects. They are souls Jesus sees and loves. May we be faithful and gentle.","needle":"people are not projects"},
 {"postId":"DZomb4ADd1F","url":"https://www.instagram.com/youversion/p/DZomb4ADd1F/","account":"youversion","sourceDetail":"profile:youversion","reason":"Micah 6:8 verse post, safe and encouraging.","comment":"Micah 6:8 is such a clear mercy-filled path: justice, kindness, and a humble walk with God. Simple words, lifelong surrender.","needle":"clear mercy-filled path"},
 {"postId":"DZoJe05jiBj","url":"https://www.instagram.com/youversion/p/DZoJe05jiBj/","account":"youversion","sourceDetail":"profile:youversion","reason":"Daniel 6 trust-in-God encouragement.","comment":"Daniel 6 is such a strong reminder that trust is not denial. It is resting in God’s faithfulness even in the den. He is trustworthy here too.","needle":"trust is not denial"},
 {"postId":"DZm_IHnDgcP","url":"https://www.instagram.com/youversion/p/DZm_IHnDgcP/","account":"youversion","sourceDetail":"profile:youversion","reason":"1 Peter 4:8 verse post about love covering sins.","comment":"1 Peter 4:8 is a needed word. Deep love covers so much because it keeps choosing mercy over keeping score. That kind of love looks like Jesus.","needle":"mercy over keeping score"},
 {"postId":"DZpcyz-kdqd","url":"https://www.instagram.com/proverbs31ministries/p/DZpcyz-kdqd/","account":"proverbs31ministries","sourceDetail":"profile:proverbs31ministries","reason":"Anxiety and faith post with 1 Peter 5:7, calls for tender encouragement.","comment":"This is tender and true. 1 Peter 5:7 feels so personal here: He does not shame anxious hearts. He cares, receives, and carries what feels too heavy for us.","needle":"does not shame anxious hearts"},
 {"postId":"DZhucPMiYIH","url":"https://www.instagram.com/proverbs31ministries/p/DZhucPMiYIH/","account":"proverbs31ministries","sourceDetail":"profile:proverbs31ministries","reason":"Grace-based reminder that God’s goodness is not earned by striving.","comment":"Such a needed reminder. God’s goodness is not wages for perfect striving. It flows from His character, and Christ is kind enough to hold us together.","needle":"not wages for perfect striving"},
]

def load_log():
    if os.path.exists(LOG):
        try: return json.load(open(LOG))
        except Exception: return {"comments":[],"errors":[]}
    return {"comments":[],"errors":[]}

def save_log(log):
    Path(LOG).parent.mkdir(parents=True, exist_ok=True)
    tmp=LOG+'.tmp'
    with open(tmp,'w',encoding='utf-8') 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')}

def warn(body):
    m=WARNING_RE.search(body or '')
    return m.group(0) if m else ''

async def focus_comment_box(b):
    js=r"""
    (() => {
      const visible = el => !!(el && (el.offsetWidth || el.offsetHeight || el.getClientRects().length));
      let ta = [...document.querySelectorAll('textarea')].find(t => /comment/i.test(t.getAttribute('aria-label')||'') && visible(t));
      if (!ta) {
        const btns=[...document.querySelectorAll('[role="button"], button, svg[aria-label*="Comment" i]')];
        for (const x of btns) { const a=(x.getAttribute('aria-label')||''); const t=(x.textContent||''); if (/comment/i.test(a+t)) { x.scrollIntoView({behavior:'instant', block:'center'}); x.click(); break; } }
      }
      window.scrollBy(0, 350);
      ta = [...document.querySelectorAll('textarea')].find(t => /comment/i.test(t.getAttribute('aria-label')||'') && visible(t));
      if (!ta) return {found:false, textareas:[...document.querySelectorAll('textarea')].map(t=>({aria:t.getAttribute('aria-label'), value:t.value, visible:visible(t)}))};
      ta.scrollIntoView({behavior:'instant', block:'center'});
      ta.click(); ta.focus();
      return {found:true, aria:ta.getAttribute('aria-label'), value:ta.value||''};
    })()
    """
    return await b.eval(js)

async def post_comment(b, comment):
    for attempt in range(3):
        r=await focus_comment_box(b)
        if r and r.get('found'):
            await asyncio.sleep(1)
            await b.send('Input.insertText', {'text': comment})
            await asyncio.sleep(2)
            val=await b.eval("(() => { const a=document.activeElement; return {tag:a&&a.tagName, value:a&&('value' in a ? a.value : a.textContent), text: document.body.innerText.slice(0,500)} })()")
            # Click exact Post button
            clicked=await b.eval(r"""
            (() => {
              const candidates=[...document.querySelectorAll('[role="button"], button')];
              for (const btn of candidates) {
                const t=(btn.textContent||'').trim();
                const disabled=btn.getAttribute('aria-disabled')==='true' || btn.disabled;
                if (t === 'Post' && !disabled) { btn.scrollIntoView({behavior:'instant', block:'center'}); btn.click(); return {clicked:true, text:t}; }
              }
              return {clicked:false, buttons:candidates.slice(-20).map(b=>({t:(b.textContent||'').trim(), dis:b.getAttribute('aria-disabled')||b.disabled||false}))};
            })()
            """)
            return {'focus':r, 'value':val, 'clicked':clicked}
        await asyncio.sleep(2)
    return {'focus':r, 'clicked':{'clicked':False}}

async def main():
    cbm_cdp.launch_profile()
    b=await cbm_cdp.connect(prefer_instagram=True)
    done=done_ids()
    posted=[]; skipped=[]; stopped=None
    try:
      for idx,t in enumerate(TARGETS):
        if len(posted)>=8: break
        if t['postId'] in done:
            skipped.append({'postId':t['postId'],'url':t['url'],'reason':'already in verified log'}); continue
        print('NAV', t['postId'], t['url'], flush=True)
        await b.goto(t['url'], wait=random.uniform(5,8))
        body=await b.eval('document.body.innerText') or ''
        w=warn(body)
        if w:
            stopped=f"Instagram warning/friction before posting {t['postId']}: {w}"; break
        if re.search(r'vincent\.emmanuel\.lee|Vincent Emmanuel Lee', body):
            skipped.append({'postId':t['postId'],'url':t['url'],'reason':'Vincent already visibly commented'}); continue
        res=await post_comment(b, t['comment'])
        print('POST_CLICK', t['postId'], json.dumps(res)[:800], flush=True)
        if not (res.get('clicked') or {}).get('clicked'):
            stopped=f"Could not click Post for {t['postId']}"; break
        await asyncio.sleep(random.uniform(5,8))
        body2=await b.eval('document.body.innerText') or ''
        w=warn(body2)
        if w:
            stopped=f"Instagram warning/friction after posting {t['postId']}: {w}"; break
        ok_user=bool(re.search(r'vincent\.emmanuel\.lee|Vincent Emmanuel Lee', body2))
        ok_text=t['needle'] in body2 or t['comment'][:40] in body2
        screenshot=''  # skip screenshot in long CBM run to avoid CDP 1009 transport failures after DOM verification
        if ok_user and ok_text:
            log=load_log()
            log.setdefault('comments',[]).append({
              'timestamp': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
              'startedAt': START,
              'source':'cbm-agent-cron',
              'postId':t['postId'],
              'url':await b.eval('location.href'),
              'comment':t['comment'],
              'account':t['account'],
              'sourceHashtag':t['sourceDetail'],
              'reason':t['reason'],
              'verified':True,
              'screenshot':screenshot,
              'verificationMethod':'CBM CDP DOM text match'
            })
            save_log(log)
            posted.append({**t, 'url':await b.eval('location.href'), 'screenshot':screenshot, 'verification':'PASS: username and distinctive comment substring found in DOM'})
            print('VERIFIED', len(posted), t['postId'], flush=True)
            if len(posted)<8:
                pause=random.uniform(22,42)
                print('PAUSE', round(pause,1), flush=True)
                await asyncio.sleep(pause)
        else:
            stopped=f"DOM verification failed for {t['postId']} (username={ok_user}, text={ok_text})"
            break
    finally:
      try: await b.close()
      except Exception: pass
    out={'startedAt':START,'postedCount':len(posted),'posted':posted,'skipped':skipped,'stopped':stopped}
    Path('/tmp/ig_post_results.json').write_text(json.dumps(out,indent=2,ensure_ascii=False))
    print('RESULT_JSON', json.dumps(out, ensure_ascii=False), flush=True)
asyncio.run(main())
