#!/usr/bin/env python3
from __future__ import annotations
import csv, json, os, time, re
from pathlib import Path
from datetime import datetime, timezone
import requests

ROOT=Path('/root/projects/etsy-quiet-faith-collection/generated/premium_50_originals_no_resize')
EXP=ROOT/'printify_nonshirt_product_expansion'
CREATED=EXP/'created_nonshirt_products.json'
PARTIAL=EXP/'publish_nonshirt_results_partial.json'
FULL=EXP/'publish_nonshirt_results.json'
STATE=EXP/'paced_publish_state.json'
ACTIVE=EXP/'paced_active_etsy_listings.json'
ADS=EXP/'etsy_ads_pending_queue.json'
ENV=Path('/root/.hermes/.env')

def load_env():
    vals={}
    if ENV.exists():
        for line in ENV.read_text().splitlines():
            s=line.strip()
            if not s or s.startswith('#') or '=' not in s: continue
            k,v=s.split('=',1); vals[k.strip()]=v.strip().strip('"').strip("'"); os.environ.setdefault(k.strip(),vals[k.strip()])
    return vals
load_env()
PRINTIFY_TOKEN=os.environ['PRINTIFY_API_TOKEN']
PRINTIFY_SHOP=os.environ.get('PRINTIFY_SHOP_ID','27001435')
PBASE='https://api.printify.com/v1'
PH={'Authorization':f'Bearer {PRINTIFY_TOKEN}','Content-Type':'application/json'}
PAYLOAD={'title': True,'description': True,'images': True,'variants': True,'tags': True,'keyFeatures': True,'shipping_template': True}

def api(method,url,headers=None,**kw):
    for a in range(1,4):
        try:
            r=requests.request(method,url,headers=headers or PH,timeout=120,**kw)
            if r.status_code in (429,500,502,503,504) and a<3:
                time.sleep(4*a); continue
            return r
        except requests.RequestException:
            if a==3: raise
            time.sleep(4*a)

def read_json(path, default):
    if not path.exists(): return default
    try: return json.load(open(path))
    except Exception: return default

def write_json(path, data):
    path.write_text(json.dumps(data,indent=2))

def save_env_var(key, val):
    lines=ENV.read_text().splitlines() if ENV.exists() else []
    found=False; out=[]
    for line in lines:
        if line.startswith(key+'='):
            out.append(f'{key}={val}'); found=True
        else: out.append(line)
    if not found: out.append(f'{key}={val}')
    ENV.write_text('\n'.join(out)+'\n')
    os.environ[key]=val

def refresh_etsy_token():
    client=os.environ.get('ETSY_CLIENT_ID'); refresh=os.environ.get('ETSY_REFRESH_TOKEN'); secret=os.environ.get('ETSY_CLIENT_SECRET')
    if not client or not refresh: return False
    data={'grant_type':'refresh_token','client_id':client,'refresh_token':refresh}
    if secret: data['client_secret']=secret
    r=requests.post('https://api.etsy.com/v3/public/oauth/token',data=data,timeout=30)
    if r.status_code!=200: return False
    d=r.json(); save_env_var('ETSY_ACCESS_TOKEN',d['access_token'])
    if d.get('refresh_token'): save_env_var('ETSY_REFRESH_TOKEN',d['refresh_token'])
    if d.get('expires_in'): save_env_var('ETSY_TOKEN_EXPIRES_AT',str(int(time.time()+int(d['expires_in']))))
    return True

def etsy_public_key():
    client=os.environ.get('ETSY_CLIENT_ID'); secret=os.environ.get('ETSY_CLIENT_SECRET')
    return f'{client}:{secret}' if client and secret else client

def get_etsy_listing(listing_id):
    key=etsy_public_key()
    if not key: return None, 'missing_key'
    r=requests.get(f'https://openapi.etsy.com/v3/application/listings/{listing_id}',headers={'x-api-key':key},timeout=30)
    if r.status_code==200: return r.json(), None
    return None, f'{r.status_code}:{r.text[:120]}'

def activate_listing(listing_id):
    client=os.environ.get('ETSY_CLIENT_ID'); token=os.environ.get('ETSY_ACCESS_TOKEN'); shop=os.environ.get('ETSY_SHOP_ID')
    if not client or not token or not shop: return False, 'missing_oauth'
    h={'x-api-key':client,'Authorization':f'Bearer {token}','Content-Type':'application/json'}
    url=f'https://openapi.etsy.com/v3/application/shops/{shop}/listings/{listing_id}'
    r=requests.patch(url,headers=h,json={'state':'active'},timeout=30)
    if r.status_code==401 and refresh_etsy_token():
        h['Authorization']='Bearer '+os.environ['ETSY_ACCESS_TOKEN']
        r=requests.patch(url,headers=h,json={'state':'active'},timeout=30)
    return r.status_code in (200,201), f'{r.status_code}:{r.text[:160]}'

def main():
    created=read_json(CREATED,[])
    results=read_json(PARTIAL, read_json(FULL, []))
    state=read_json(STATE, {'runs':[]})
    active=read_json(ACTIVE, [])
    ads=read_json(ADS, [])
    submitted_ids={r['printify_product_id'] for r in results if r.get('publish_status') in ('submitted','already_linked') or (r.get('http') and int(r.get('http'))<300)}
    active_ids={str(x.get('listing_id')) for x in active}
    ads_ids={str(x.get('listing_id')) for x in ads}

    new_linked=[]; activated=[]; still_pending=0
    # Poll a bounded number each run to avoid API hammering, newest + older.
    poll_candidates=[r for r in results if r.get('printify_product_id') in submitted_ids]
    for r in poll_candidates[:120]:
        pid=r['printify_product_id']
        g=api('GET',f'{PBASE}/shops/{PRINTIFY_SHOP}/products/{pid}.json')
        if g.status_code!=200: continue
        d=g.json(); ext=d.get('external') or {}
        lid=str(ext.get('id') or '')
        handle=ext.get('handle') or ''
        if not lid:
            still_pending+=1; continue
        if lid not in active_ids:
            info,err=get_etsy_listing(lid)
            state_now=(info or {}).get('state')
            if state_now!='active':
                ok,msg=activate_listing(lid)
                if ok:
                    info,_=get_etsy_listing(lid); state_now=(info or {}).get('state')
                    activated.append({'listing_id':lid,'title':d.get('title'),'state':state_now,'url':handle or (info or {}).get('url')})
            if state_now=='active':
                row={'listing_id':lid,'printify_product_id':pid,'title':d.get('title'),'lane':r.get('lane'),'phrase':r.get('phrase'),'url':handle or (info or {}).get('url'),'verified_at':datetime.now(timezone.utc).isoformat()}
                active.append(row); active_ids.add(lid); new_linked.append(row)
                if lid not in ads_ids:
                    ads.append({**row,'ads_status':'pending_shop_manager_enable','note':'Etsy Ads controls are not exposed through this API session; enable in Shop Manager Ads UI.'}); ads_ids.add(lid)

    # Publish exactly one new product per scheduler tick.
    next_product=None
    for p in created:
        if p['printify_product_id'] not in submitted_ids:
            next_product=p; break
    published=None; error=None
    if next_product:
        pid=next_product['printify_product_id']
        pr=api('POST',f'{PBASE}/shops/{PRINTIFY_SHOP}/products/{pid}/publish.json',json=PAYLOAD)
        try: body=pr.json() if pr.text else {}
        except Exception: body={'raw':pr.text[:500]}
        published={**next_product,'publish_status':'submitted' if pr.status_code<300 else 'error','http':pr.status_code,'response':body,'submitted_at':datetime.now(timezone.utc).isoformat()}
        results.append(published)
        if pr.status_code>=300: error=published

    write_json(PARTIAL, results); write_json(STATE, state); write_json(ACTIVE, active); write_json(ADS, ads)
    remaining=len([p for p in created if p['printify_product_id'] not in {r['printify_product_id'] for r in results if r.get('publish_status')!='error'}])
    summary={'time':datetime.now(timezone.utc).isoformat(),'published_this_tick': bool(published and not error),'published_title': (published or {}).get('title'),'published_http': (published or {}).get('http'),'new_active_verified':len(new_linked),'activated_this_tick':len(activated),'active_total_recorded':len(active),'ads_pending_total':len(ads),'remaining_to_submit':remaining,'still_pending_external_sample_count':still_pending,'error':error}
    state.setdefault('runs',[]).append(summary); write_json(STATE,state)
    print('Premium product paced publish tick:')
    if published and not error: print(f"Submitted 1 product to Etsy via Printify: {published['title']}")
    elif not next_product: print('No remaining products to submit.')
    else: print(f"Publish error: {error}")
    print(f"New Etsy-active verified this tick: {len(new_linked)}; total active recorded: {len(active)}")
    print(f"Remaining to submit at 10-minute pace: {remaining}")
    print(f"Ads queue pending: {len(ads)} (Shop Manager login required to toggle Etsy Ads; API queue is ready)")

if __name__=='__main__': main()
