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

ROOT=Path('/root/projects/etsy-quiet-faith-collection/generated/premium_50_originals_no_resize')
EXP=ROOT/'personalized_50_product_family_options'/'text_on_picture_style_launch_assets'/'printify_first50_personalized'
CREATED=EXP/'created_personalized_first50.json'
RESULTS=EXP/'publish_personalized_first50_results.json'
STATE=EXP/'publish_personalized_first50_state.json'
ACTIVE=EXP/'active_personalized_etsy_listings.json'
ENV=Path('/root/.hermes/.env')
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); os.environ.setdefault(k.strip(), v.strip().strip('"').strip("'"))
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'}
PUBLISH_PAYLOAD={'title':True,'description':True,'images':True,'variants':True,'tags':True,'keyFeatures':True,'shipping_template':True}
PERS_INSTRUCTIONS='Enter the recipient name exactly as you want it printed. Example: Sarah. Max 20 characters. We will copy/paste exactly from your entry.'

def read_json(path, default):
    try: return json.loads(path.read_text())
    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 []
    out=[]; found=False
    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'); secret=os.environ.get('ETSY_CLIENT_SECRET'); refresh=os.environ.get('ETSY_REFRESH_TOKEN')
    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_key(include_secret=True):
    client=os.environ.get('ETSY_CLIENT_ID'); secret=os.environ.get('ETSY_CLIENT_SECRET')
    return f'{client}:{secret}' if include_secret and client and secret else client

def etsy_headers(oauth=False):
    h={'x-api-key':etsy_key(True)}
    if oauth: h['Authorization']='Bearer '+os.environ.get('ETSY_ACCESS_TOKEN','')
    h['Content-Type']='application/json'
    return h

def p_api(method,url,**kw):
    for a in range(1,4):
        r=requests.request(method,url,headers=PH,timeout=180,**kw)
        if r.status_code in (429,500,502,503,504) and a<3:
            time.sleep(5*a); continue
        return r

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

def patch_etsy_listing(lid, payload):
    shop=os.environ.get('ETSY_SHOP_ID')
    url=f'https://openapi.etsy.com/v3/application/shops/{shop}/listings/{lid}'
    r=requests.patch(url,headers=etsy_headers(True),json=payload,timeout=30)
    if r.status_code==401 and refresh_etsy_token():
        r=requests.patch(url,headers=etsy_headers(True),json=payload,timeout=30)
    return r

def create_or_update_personalization(lid):
    shop=os.environ.get('ETSY_SHOP_ID')
    url=f'https://openapi.etsy.com/v3/application/shops/{shop}/listings/{lid}/personalization'
    payload={'personalization_questions':[{
        'question_text':'Recipient name',
        'instructions':'Enter the name exactly as you want it printed. Example: Sarah. Max 20 characters.',
        'question_type':'text_input',
        'required':True,
        'max_allowed_characters':20,
    }]}
    r=requests.post(url,headers=etsy_headers(True),json=payload,timeout=30)
    if r.status_code==401 and refresh_etsy_token():
        r=requests.post(url,headers=etsy_headers(True),json=payload,timeout=30)
    return r

def get_personalization(lid):
    r=requests.get(f'https://openapi.etsy.com/v3/application/listings/{lid}/personalization',headers={'x-api-key':etsy_key(True)},timeout=30)
    return r

def activate_and_personalize(lid):
    results={}
    r=patch_etsy_listing(lid, {'state':'active'})
    results['activate_http']=r.status_code; results['activate_body']=safe_body(r)
    # Etsy deprecated legacy listing personalization fields in 2026.
    # Use the dedicated personalization endpoint instead.
    r2=create_or_update_personalization(lid)
    results['personalization_http']=r2.status_code; results['personalization_body']=safe_body(r2)
    r3=get_personalization(lid)
    results['personalization_verify_http']=r3.status_code; results['personalization_verify_body']=safe_body(r3)
    return results

def safe_body(r):
    try: return r.json()
    except Exception: return r.text[:500]

def main():
    created=read_json(CREATED,[]); results=read_json(RESULTS,[]); state=read_json(STATE,{'runs':[]}); active=read_json(ACTIVE,[])
    submitted={r['printify_product_id'] for r in results if r.get('publish_status')!='error'}
    active_lids={str(x.get('listing_id')) for x in active}

    new_active=[]; personalization_attempts=[]
    # poll submitted products for linkage and patch active/personalization
    for row in results[:80]:
        pid=row.get('printify_product_id')
        if not pid: continue
        g=p_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 '')
        if not lid or lid in active_lids: continue
        patch=activate_and_personalize(lid)
        info,err=get_etsy_listing(lid)
        rec={'listing_id':lid,'printify_product_id':pid,'title':d.get('title'),'url':ext.get('handle') or (info or {}).get('url'),'state':(info or {}).get('state'),'is_personalizable':(info or {}).get('is_personalizable'),'personalization_is_required':(info or {}).get('personalization_is_required'),'patch':patch,'verified_at':datetime.now(timezone.utc).isoformat()}
        active.append(rec); active_lids.add(lid); new_active.append(rec); personalization_attempts.append(patch)

    next_product=None
    for p in created:
        pid=p.get('printify_product_id')
        if pid and pid not in submitted:
            next_product=p; break
    published=None; error=None
    if next_product:
        pid=next_product['printify_product_id']
        pr=p_api('POST',f'{PBASE}/shops/{PRINTIFY_SHOP}/products/{pid}/publish.json',json=PUBLISH_PAYLOAD)
        published={k:next_product.get(k) for k in ['sku_product','product_type','base_phrase','title','printify_product_id','lane']}
        published.update({'publish_status':'submitted' if pr.status_code<300 else 'error','http':pr.status_code,'submitted_at':datetime.now(timezone.utc).isoformat(),'response':safe_body(pr)})
        results.append(published)
        if pr.status_code>=300: error=published

    remaining=len([p for p in created if p.get('printify_product_id') not in {r.get('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_active),'active_total_recorded':len(active),'remaining_to_submit':remaining,'error':error}
    state.setdefault('runs',[]).append(summary)
    write_json(RESULTS,results); write_json(ACTIVE,active); write_json(STATE,state)
    print('Personalized text-on-picture publish tick:')
    if published and not error: print(f"Submitted 1 personalized product: {published['title']}")
    elif not next_product: print('No remaining personalized products to submit.')
    else: print(f'Publish error: {error}')
    print(f'New Etsy-active verified this tick: {len(new_active)}; active total recorded: {len(active)}')
    print(f'Remaining personalized products to submit: {remaining}')

if __name__=='__main__': main()
