#!/usr/bin/env python3
from __future__ import annotations
import base64, csv, json, os, time, zipfile
from datetime import date
from pathlib import Path
import requests
from PIL import Image, ImageDraw, ImageFont

ROOT=Path('/root/etsy_printify_revenue_engine')
TODAY=date.today().isoformat()
BATCH=f'{TODAY}-multi-product-batch-2'
ASSET_DIR=ROOT/'assets'/'designs'/BATCH
QA_DIR=ROOT/'qa'/'visual'/BATCH
PROD_DIR=ROOT/'printify_products'/BATCH
DIG_DIR=ROOT/'digital_products'/BATCH
LISTING_DIR=ROOT/'listings'
for d in [ASSET_DIR,QA_DIR,PROD_DIR,DIG_DIR,LISTING_DIR]: d.mkdir(parents=True, exist_ok=True)

FONT_BOLD='/usr/share/fonts/truetype/dejavu/DejaVuSansCondensed-Bold.ttf'
FONT_SANS='/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf'
FONT_SERIF='/usr/share/fonts/truetype/dejavu/DejaVuSerif-Bold.ttf'
FONT_SERIF_REG='/usr/share/fonts/truetype/dejavu/DejaVuSerif.ttf'
CREAM=(248,239,216,255); GOLD=(205,164,83,255); DARK=(31,30,28,255); FOREST=(42,72,56,255); BLUE=(30,49,70,255); BURG=(96,34,49,255); RUST=(135,74,45,255); IVORY=(248,242,231,255)

def font(path,size): return ImageFont.truetype(path,size)
def bbox(draw,text,fnt,spacing=8):
    b=draw.multiline_textbbox((0,0),text,font=fnt,spacing=spacing,align='center'); return b[2]-b[0], b[3]-b[1]
def fit(draw,text,path,max_w,start,min_size=70,spacing=8):
    for s in range(start,min_size-1,-8):
        f=font(path,s)
        if bbox(draw,text,f,spacing)[0]<=max_w: return f
    return font(path,min_size)
def center(draw,W,y,text,fnt,fill,spacing=12):
    b=draw.multiline_textbbox((0,0),text,font=fnt,spacing=spacing,align='center')
    draw.multiline_text(((W-(b[2]-b[0]))/2,y),text,font=fnt,fill=fill,spacing=spacing,align='center')
    return y+(b[3]-b[1])
def cross(draw,cx,cy,s,fill):
    w=int(42*s); h=int(180*s); arm=int(138*s); ah=int(38*s)
    draw.rounded_rectangle((cx-w//2,cy-h//2,cx+w//2,cy+h//2),radius=max(5,w//3),fill=fill)
    draw.rounded_rectangle((cx-arm//2,cy-ah//2,cx+arm//2,cy+ah//2),radius=max(5,ah//3),fill=fill)
def branch(draw,cx,cy,s,fill):
    draw.line((cx,cy,cx,cy+780*s),fill=fill,width=int(22*s))
    for i in range(7):
        y=cy+80*s+i*95*s; side=-1 if i%2==0 else 1
        x2=cx+side*(185*s+i*14*s)
        draw.line((cx,y,x2,y-55*s),fill=fill,width=int(18*s))
        draw.ellipse((min(x2,x2+side*150*s),y-115*s,max(x2,x2+side*150*s),y-20*s),outline=fill,width=int(11*s))
def save_png(img,path): img.save(path,optimize=True); return str(path)

def apparel_peace(path):
    W,H=4500,5400; img=Image.new('RGBA',(W,H),(0,0,0,0)); d=ImageDraw.Draw(img)
    branch(d,820,760,1.4,GOLD); branch(d,3680,760,1.4,GOLD)
    center(d,W,800,'PEACE',font(FONT_SERIF,690),CREAM,8)
    center(d,W,1565,'GUARDS',fit(d,'GUARDS',FONT_BOLD,3350,540),GOLD,8)
    center(d,W,2210,'MY MIND',fit(d,'MY MIND',FONT_SERIF,3600,540),CREAM,8)
    d.line((950,3100,3550,3100),fill=GOLD,width=18)
    center(d,W,3270,'PHILIPPIANS 4:7',font(FONT_SERIF_REG,170),GOLD,8)
    center(d,W,3570,'CALM IS COVERED',font(FONT_BOLD,145),CREAM,8)
    return save_png(img,path)

def tee_work(path):
    W,H=4500,5400; img=Image.new('RGBA',(W,H),(0,0,0,0)); d=ImageDraw.Draw(img)
    cross(d,W//2,705,1.25,DARK)
    d.line((1150,970,3350,970),fill=RUST,width=18)
    center(d,W,1140,'WORK',font(FONT_SERIF,650),DARK,8)
    center(d,W,1850,'AS',font(FONT_BOLD,290),RUST,8)
    center(d,W,2215,'WORSHIP',fit(d,'WORSHIP',FONT_SERIF,3650,540),DARK,8)
    d.rounded_rectangle((680,3100,3820,3500),radius=55,outline=RUST,width=12)
    center(d,W,3155,'COLOSSIANS 3:23',font(FONT_SERIF_REG,165),RUST,8)
    center(d,W,3710,'DILIGENCE WITH DEVOTION',font(FONT_BOLD,135),DARK,8)
    return save_png(img,path)

def sweatshirt_grace(path):
    W,H=4500,5400; img=Image.new('RGBA',(W,H),(0,0,0,0)); d=ImageDraw.Draw(img)
    for r in range(5): d.arc((760-r*34,630-r*34,3740+r*34,3620+r*34),205,335,fill=FOREST,width=10)
    center(d,W,980,'GRACE',font(FONT_SERIF,660),FOREST,8)
    center(d,W,1705,'FOR',font(FONT_BOLD,250),RUST,8)
    center(d,W,2025,'TODAY',font(FONT_SERIF,670),FOREST,8)
    d.line((1100,2940,3400,2940),fill=RUST,width=16)
    center(d,W,3130,'2 CORINTHIANS 12:9',font(FONT_SERIF_REG,155),RUST,8)
    center(d,W,3405,'ENOUGH FOR THIS STEP',font(FONT_BOLD,130),FOREST,8)
    return save_png(img,path)

def mug(path, phrase, ref, ink=DARK, accent=GOLD):
    W,H=2700,1125; img=Image.new('RGBA',(W,H),(0,0,0,0)); d=ImageDraw.Draw(img)
    for cx in (675,2025):
        cross(d,cx,175,0.42,accent)
        d.line((cx-380,280,cx+380,280),fill=accent,width=8)
        words=phrase.upper().replace(' BEFORE ','\nBEFORE\n').replace(' HAS A ',' HAS A\n').replace(' FOR ','\nFOR\n')
        f=fit(d,words,FONT_SERIF,880,150,66,6)
        b=d.multiline_textbbox((0,0),words,font=f,spacing=6,align='center')
        d.multiline_text((cx-(b[2]-b[0])/2,330),words,font=f,fill=ink,spacing=6,align='center')
        rf=font(FONT_SERIF_REG,54); rb=d.textbbox((0,0),ref,font=rf)
        d.text((cx-(rb[2]-rb[0])/2,875),ref,font=rf,fill=accent)
    return save_png(img,path)

def notebook(path):
    W,H=4500,5700; img=Image.new('RGBA',(W,H),IVORY); d=ImageDraw.Draw(img)
    d.rounded_rectangle((260,260,4240,5440),radius=82,outline=BLUE,width=30)
    d.rounded_rectangle((510,555,3990,5145),radius=46,outline=GOLD,width=10)
    cross(d,W//2,820,1.15,BLUE)
    center(d,W,1210,'PRAYER\nBEFORE\nTHE MEETING',font(FONT_SERIF,460),BLUE,20)
    d.line((1060,3380,3440,3380),fill=GOLD,width=18)
    center(d,W,3580,'WORK NOTES + PRAYER JOURNAL',font(FONT_BOLD,205),DARK,8)
    center(d,W,3970,'START WITH WISDOM',font(FONT_SERIF_REG,165),BLUE,8)
    center(d,W,4540,'James 1:5',font(FONT_SERIF_REG,135),DARK,8)
    return save_png(img,path)

def poster(path):
    W,H=4800,6000; img=Image.new('RGB',(W,H),(248,241,229)); d=ImageDraw.Draw(img)
    d.rectangle((270,270,W-270,H-270),outline=(41,70,54),width=20)
    d.rectangle((430,430,W-430,H-430),outline=(205,164,83),width=8)
    branch(d,760,720,1.5,(41,70,54,255)); branch(d,4040,720,1.5,(41,70,54,255))
    center(d,W,1120,'ROOTED\nIN MERCY',font(FONT_SERIF,545),(35,32,28),20)
    d.line((1120,2880,3680,2880),fill=(205,164,83),width=18)
    verse='His mercies are new every morning;\ngreat is Your faithfulness.'
    center(d,W,3180,verse,font(FONT_SERIF_REG,170),(35,32,28),18)
    center(d,W,4130,'LAMENTATIONS 3:22–23',font(FONT_BOLD,205),(96,34,49),10)
    center(d,W,4650,'MORNING MERCY DECLARATION',font(FONT_SANS,118),(41,70,54),10)
    img.save(path,quality=95)
    return str(path)

products=[]
def add(**kw): products.append(kw)
peace=ASSET_DIR/'peace-guards-my-mind-hoodie-4500x5400.png'; apparel_peace(peace)
work=ASSET_DIR/'work-as-worship-tee-4500x5400.png'; tee_work(work)
grace=ASSET_DIR/'grace-for-today-sweatshirt-4500x5400.png'; sweatshirt_grace(grace)
peace_mug=ASSET_DIR/'peace-has-a-guard-mug-2700x1125.png'; mug(peace_mug,'Peace Has A Guard','Philippians 4:7',BLUE,GOLD)
grace_mug=ASSET_DIR/'Grace-for-today-mug-2700x1125.png'; mug(grace_mug,'Grace For Today','2 Cor. 12:9',FOREST,GOLD)
nb=ASSET_DIR/'prayer-before-the-meeting-notebook-4500x5700.png'; notebook(nb)
post=ASSET_DIR/'rooted-in-mercy-poster-4800x6000.jpg'; poster(post)

add(key='peace_hoodie', lane='hoodie', title='Peace Guards My Mind Hoodie | Christian Anxiety Prayer Sweatshirt', path=str(peace), blueprint_id=77, provider_id=99, colors=['Black','Dark Heather','Navy'], sizes=['S','M','L','XL','2XL'], price=4499, price_2xl=4699, tags=['christian hoodie','peace hoodie','philippians 4 7','prayer hoodie','anxiety prayer','faith hoodie','christian gift','bible verse hoodie','jesus hoodie','church hoodie','faith sweatshirt','prayer warrior','peace of god'], description='Original Christian peace declaration hoodie inspired by Philippians 4:7, built with high-contrast cream and gold ink for dark fleece colors.')
add(key='work_tee', lane='tshirt', title='Work As Worship Christian T-Shirt | Colossians 3:23 Faith Tee', path=str(work), blueprint_id=706, provider_id=99, colors=['Ivory','White','Bay'], sizes=['S','M','L','XL','2XL'], price=2499, price_2xl=2699, tags=['christian shirt','work as worship','faith tee','colossians 3 23','christian gift','bible verse shirt','jesus shirt','church shirt','christian apparel','faith based gift','christian men','christian women','work faith'], description='Original work-as-worship Christian tee artwork with dark ink for light Comfort Colors-style shirts. Inspired by Colossians 3:23.')
add(key='grace_sweatshirt', lane='sweatshirt', title='Grace For Today Sweatshirt | Christian Women Faith Sweatshirt', path=str(grace), blueprint_id=49, provider_id=99, colors=['Sand','Ash','Sport Grey'], sizes=['S','M','L','XL','2XL'], price=3999, price_2xl=4299, tags=['christian sweatshirt','grace sweatshirt','2 corinthians 12 9','faith sweatshirt','christian women','prayer gift','bible verse sweatshirt','jesus sweatshirt','church sweatshirt','womens faith gift','grace for today','faith apparel','christian gift'], description='Original Grace For Today sweatshirt design with dark green/rust ink for light fleece. Inspired by 2 Corinthians 12:9.')
add(key='peace_mug', lane='mug', title='Peace Has A Guard Mug | Philippians 4:7 Christian Coffee Cup', path=str(peace_mug), blueprint_id=68, provider_id=1, variants='all', price=1699, tags=['christian mug','peace mug','philippians 4 7','prayer mug','faith coffee','coffee and prayer','christian gift','anxiety prayer','bible verse mug','jesus mug','church gift','faith mug','peace of god'], description='Wrap-safe Christian coffee mug artwork: Peace Has A Guard with Philippians 4:7 reference, repeated for left/right front views.')
add(key='grace_mug', lane='mug', title='Grace For Today Mug | Christian Encouragement Coffee Cup', path=str(grace_mug), blueprint_id=425, provider_id=1, variants='all', price=1899, tags=['christian mug','grace mug','grace for today','2 corinthians 12 9','faith coffee','christian gift','encouragement mug','bible verse mug','jesus mug','church gift','faith mug','coffee and prayer','prayer cup'], description='15oz Christian encouragement mug with original Grace For Today declaration and dark high-contrast type for white ceramic.')
add(key='meeting_notebook', lane='office', title='Prayer Before The Meeting Journal | Christian Work Notebook', path=str(nb), blueprint_id=74, provider_id=1, variants='all', price=1899, tags=['christian notebook','prayer journal','faith office','work notebook','prayer before work','christian journal','james 1 5','bible study notebook','church gift','christian gift','prayer notebook','faith journal','desk faith'], description='Professional Christian office notebook cover for work notes and prayer: Prayer Before The Meeting, Start With Wisdom, James 1:5.')
add(key='mercy_poster', lane='wall_art', title='Rooted In Mercy Wall Art | Lamentations 3 Christian Poster', path=str(post), blueprint_id=282, provider_id=99, variant_filter={'size':['11″ x 14″','12″ x 18″','16″ x 20″','18″ x 24″'],'paper':['Matte']}, price=1299, tags=['christian wall art','lamentations 3','mercy wall art','scripture print','christian poster','morning mercy','bible verse art','faith wall decor','christian home','prayer poster','christian gift','printable art','faith poster'], description='Premium Christian poster layout for Lamentations 3:22-23 with warm ivory paper style, original typography, and readable wall-art hierarchy.')

research={'date':TODAY,'batch':BATCH,'sources':['Existing cross-product batch plan and multi-product expansion process','Prior successful GracedPrintCo strongest-theme digital set','Public marketplace demand fallback: prayer/anxiety, peace, Christian work/office gifts, grace encouragement, scripture wall decor, mugs as low-price gift entry points'],'demand_signals':['Philippians peace/anxiety phrases fit hoodie and mug gift searches','Christian office/entrepreneur language supports notebook and tee lanes','Grace encouragement phrases fit women’s sweatshirts and mugs','Lamentations mercy language fits printable/wall-art buyer intent','Short original declarations were chosen to keep thumbnail readability and avoid copied trend designs'],'selected_products':[{'title':p['title'],'lane':p['lane']} for p in products]}
(PROD_DIR/'research_and_selection.json').write_text(json.dumps(research,indent=2),encoding='utf-8')

bgmap={'Black':(18,18,18),'Dark Heather':(55,55,58),'Navy':(25,35,55),'Ivory':(238,229,210),'White':(248,248,244),'Bay':(145,171,151),'Sand':(224,210,181),'Ash':(228,226,218),'Sport Grey':(178,178,170),'white ceramic':(252,252,248),'cream paper':(248,241,229)}
app=[p for p in products if p['lane'] in ('hoodie','sweatshirt','tshirt')]
cellw,cellh=680,860; sheet=Image.new('RGB',(40+4*cellw,110+len(app)*cellh),(245,240,229)); sd=ImageDraw.Draw(sheet)
sd.text((40,30),f'{BATCH} apparel contrast QA',font=font(FONT_BOLD,42),fill=(25,25,25))
for r,p in enumerate(app):
    art=Image.open(p['path']).convert('RGBA')
    for c,color in enumerate(p['colors']):
        x=40+c*cellw; y=100+r*cellh
        bg=Image.new('RGBA',(4500,5400),bgmap[color]+(255,)); comp=Image.alpha_composite(bg,art).convert('RGB'); comp.thumbnail((cellw-60,cellh-120),Image.LANCZOS)
        sd.rounded_rectangle((x,y,x+cellw-30,y+cellh-35),radius=22,fill=(255,255,255),outline=(205,195,175),width=2)
        sheet.paste(comp,(x+(cellw-30-comp.width)//2,y+30))
        sd.text((x+20,y+cellh-80),p['title'][:42],font=font(FONT_BOLD,20),fill=(20,20,20))
        sd.text((x+20,y+cellh-50),color,font=font(FONT_SANS,22),fill=(50,50,50))
app_sheet=QA_DIR/f'{BATCH}_apparel_contact_sheet.jpg'; sheet.save(app_sheet,quality=92)
non=[p for p in products if p['lane'] not in ('hoodie','sweatshirt','tshirt')]
cellw,cellh=760,620; ns=Image.new('RGB',(40+2*cellw,110+((len(non)+1)//2)*cellh),(245,240,229)); nd=ImageDraw.Draw(ns)
nd.text((40,30),f'{BATCH} mug/office/wall-art QA',font=font(FONT_BOLD,40),fill=(25,25,25))
for i,p in enumerate(non):
    x=40+(i%2)*cellw; y=100+(i//2)*cellh
    im=Image.open(p['path']).convert('RGBA') if p['path'].endswith('.png') else Image.open(p['path']).convert('RGB')
    if im.mode=='RGBA':
        base_color=bgmap['white ceramic'] if p['lane']=='mug' else bgmap['cream paper']; im=Image.alpha_composite(Image.new('RGBA',im.size,base_color+(255,)),im).convert('RGB')
    else: im=im.convert('RGB')
    im.thumbnail((cellw-80,cellh-130),Image.LANCZOS)
    nd.rounded_rectangle((x,y,x+cellw-35,y+cellh-35),radius=22,fill=(255,255,255),outline=(205,195,175),width=2)
    ns.paste(im,(x+(cellw-35-im.width)//2,y+25))
    nd.text((x+22,y+cellh-82),p['title'][:55],font=font(FONT_BOLD,20),fill=(20,20,20))
    nd.text((x+22,y+cellh-52),p['lane'],font=font(FONT_SANS,22),fill=(50,50,50))
non_sheet=QA_DIR/f'{BATCH}_non_apparel_contact_sheet.jpg'; ns.save(non_sheet,quality=92)

qa=[]; passed=True
scripture_ok={'Philippians 4:7':'peace of God guards hearts/minds','Colossians 3:23':'work heartily as for the Lord','2 Corinthians 12:9':'grace sufficient','James 1:5':'ask God for wisdom','Lamentations 3:22-23':'mercies new every morning'}
for p in products:
    im=Image.open(p['path']); issues=[]
    if p['lane'] in ('hoodie','sweatshirt','tshirt') and im.size!=(4500,5400): issues.append('wrong_apparel_size')
    if p['lane']=='mug' and im.size!=(2700,1125): issues.append('wrong_mug_size')
    if p['lane']=='office' and im.size!=(4500,5700): issues.append('wrong_notebook_size')
    if p['lane']=='wall_art' and im.size!=(4800,6000): issues.append('wrong_poster_size')
    if im.mode=='RGBA' and not im.getchannel('A').getbbox(): issues.append('blank_transparent_art')
    low=' '.join([p['title'],p['description'],' '.join(p['tags'])]).lower()
    if any(bad in low for bad in ['disney','nike','starbucks','barbie','swift','super bowl','nfl','nba','mickey','harry potter']): issues.append('ip_risk_keyword')
    if p['lane'] in ('hoodie','sweatshirt','tshirt') and p['key']!='work_tee' and any(c in ['White','Ivory'] for c in p.get('colors',[])): issues.append('unapproved_light_apparel_combo')
    if issues: passed=False
    qa.append({'title':p['title'],'lane':p['lane'],'file':p['path'],'size':im.size,'mode':im.mode,'issues':issues or ['none']})
qa_report={'result':'PASS' if passed else 'FAIL','checks':qa,'scripture_references_checked':scripture_ok,'contact_sheets':[str(app_sheet),str(non_sheet)],'notes':['Artwork-only upload files; contact sheets are internal QA previews.','Dark garments use cream/gold high contrast; light garments use dark green/charcoal/rust ink.','Mugs use 2700x1125 wrap-safe repeated front-view layouts with handle margins.','Poster and notebook have separate product-specific layouts and margins.']}
(QA_DIR/f'{BATCH}_qa_report.json').write_text(json.dumps(qa_report,indent=2),encoding='utf-8')
if not passed: raise SystemExit('QA failed; refusing Printify upload')

# Digital wall-art candidate bundle, prepared only until direct Etsy digital access is available.
dig_files=[]
for ratio,(w,h) in {'2x3':(3600,5400),'3x4':(3600,4800),'4x5':(4800,6000),'11x14':(3300,4200)}.items():
    src=Image.open(post).convert('RGB'); src.thumbnail((w,h),Image.LANCZOS)
    canvas=Image.new('RGB',(w,h),(248,241,229)); canvas.paste(src,((w-src.width)//2,(h-src.height)//2))
    out=DIG_DIR/f'rooted-in-mercy-lamentations3-{ratio}.jpg'; canvas.save(out,quality=95); dig_files.append(out)
readme=DIG_DIR/'README-BUYER-INSTRUCTIONS.txt'; readme.write_text('Digital printable candidate prepared for Etsy direct access. No physical item is included. Print at the matching ratio or send the JPG to a local/online print shop.\n',encoding='utf-8')
zip_path=DIG_DIR/'rooted-in-mercy-lamentations3-digital-printable-candidate.zip'
with zipfile.ZipFile(zip_path,'w',zipfile.ZIP_DEFLATED) as z:
    for f in dig_files+[readme]: z.write(f,f.name)
(DIG_DIR/'etsy_listing_kit.md').write_text('# Etsy digital listing candidate\n\nTitle: Rooted In Mercy Printable Wall Art | Lamentations 3 Christian Scripture Print\n\nPrice seed: $9.99\n\nTags: christian wall art, lamentations 3, mercy wall art, scripture print, morning mercy, bible verse art, faith wall decor\n\nStatus: Prepared only; requires direct Etsy digital-file access before listing.\n',encoding='utf-8')

csv_path=LISTING_DIR/f'{BATCH}.csv'
with csv_path.open('w',newline='',encoding='utf-8') as f:
    w=csv.DictWriter(f,fieldnames=['title','lane','description','tags','price_seed','asset_file'])
    w.writeheader()
    for p in products: w.writerow({'title':p['title'],'lane':p['lane'],'description':p['description'],'tags':','.join(p['tags']),'price_seed':f"{p['price']/100:.2f}",'asset_file':p['path']})

for line in Path('/root/.hermes/.env').read_text().splitlines():
    if '=' in line and not line.strip().startswith('#'):
        k,v=line.split('=',1); os.environ.setdefault(k.strip(),v.strip().strip('"').strip("'"))
token=os.environ.get('PRINTIFY_API_TOKEN'); shop=os.environ.get('PRINTIFY_SHOP_ID','27001435')
if not token: raise SystemExit('missing PRINTIFY_API_TOKEN')
base='https://api.printify.com/v1'; headers={'Authorization':f'Bearer {token}','Content-Type':'application/json'}
image_ids={}; created=[]; payloads=[]; failures=[]; variant_cache={}
def get_variants(bp,pp):
    key=(bp,pp)
    if key not in variant_cache:
        r=requests.get(f'{base}/catalog/blueprints/{bp}/print_providers/{pp}/variants.json',headers=headers,timeout=30); r.raise_for_status()
        js=r.json(); variant_cache[key]=js.get('variants',js) if isinstance(js,dict) else js
    return variant_cache[key]
def enabled_variants(p):
    vs=get_variants(p['blueprint_id'],p['provider_id']); out=[]
    if p.get('variants')=='all':
        for v in vs: out.append({'id':v['id'],'price':p['price'],'is_enabled':True})
    elif p.get('variant_filter'):
        flt=p['variant_filter']
        for v in vs:
            opt=v.get('options',{})
            if all(opt.get(k) in vals for k,vals in flt.items()): out.append({'id':v['id'],'price':p['price'],'is_enabled':True})
    else:
        by={(v['options'].get('color'),v['options'].get('size')):v['id'] for v in vs}
        for color in p['colors']:
            for size in p['sizes']:
                vid=by.get((color,size))
                if vid: out.append({'id':vid,'price':p.get('price_2xl',p['price']) if size=='2XL' else p['price'],'is_enabled':True})
    return out
for p in products:
    try:
        data=base64.b64encode(Path(p['path']).read_bytes()).decode('ascii')
        up=requests.post(f'{base}/uploads/images.json',headers=headers,json={'file_name':Path(p['path']).name,'contents':data},timeout=120)
        if up.status_code>=300: raise RuntimeError(f'upload {up.status_code}: {up.text[:500]}')
        image_id=up.json()['id']; image_ids[p['key']]=image_id
        enabled=enabled_variants(p); vids=[v['id'] for v in enabled]
        if not enabled: raise RuntimeError('no enabled variants selected')
        desc=p['description']+'\n\nDraft created by GracedPrintCo autonomous POD factory. Artwork-only asset uploaded; Printify generates product mockups. Not published by this automation.'
        scale=0.86 if p['lane'] in ('hoodie','sweatshirt') else (0.84 if p['lane']=='tshirt' else 1.0)
        body={'title':p['title'][:140],'description':desc,'blueprint_id':p['blueprint_id'],'print_provider_id':p['provider_id'],'variants':enabled,'print_areas':[{'variant_ids':vids,'placeholders':[{'position':'front','images':[{'id':image_id,'x':0.5,'y':0.5,'scale':scale,'angle':0}]}]}],'tags':p['tags'][:13]}
        cr=requests.post(f'{base}/shops/{shop}/products.json',headers=headers,json=body,timeout=120)
        payloads.append({'title':p['title'],'payload':body,'status_code':cr.status_code,'response':cr.text[:1200]})
        if cr.status_code>=300: raise RuntimeError(f'create {cr.status_code}: {cr.text[:700]}')
        pj=cr.json(); created.append({'id':pj.get('id'),'title':pj.get('title',p['title']),'lane':p['lane'],'visible':pj.get('visible'),'status':'draft_created_not_published','image_id':image_id,'variant_count':len(enabled),'price_seed':p['price']/100})
        time.sleep(1)
    except Exception as e:
        failures.append({'title':p['title'],'lane':p['lane'],'error':str(e)})
(PROD_DIR/'uploaded_image_ids.json').write_text(json.dumps(image_ids,indent=2),encoding='utf-8')
(PROD_DIR/'created_products_summary.json').write_text(json.dumps(created,indent=2),encoding='utf-8')
(PROD_DIR/'create_product_payloads_and_responses.json').write_text(json.dumps(payloads,indent=2),encoding='utf-8')
if failures: (PROD_DIR/'printify_failures.json').write_text(json.dumps(failures,indent=2),encoding='utf-8')

summary='# Daily Multi-Product Factory Batch — '+BATCH+'\n\n'
summary+='Status: QA passed. Printify draft creation attempted for all passed physical POD assets; digital candidate prepared only for future Etsy digital access. No products were intentionally published.\n\n'
summary+=f'QA contact sheets:\n- {app_sheet}\n- {non_sheet}\n\n'
summary+='## Printify drafts created\n\n| Product | Lane | Printify product ID | Image ID | Variants | Price seed |\n|---|---|---:|---:|---:|---:|\n'
for c in created: summary+=f"| {c['title']} | {c['lane']} | {c['id']} | {c['image_id']} | {c['variant_count']} | ${c['price_seed']:.2f} |\n"
if failures:
    summary+='\n## Printify creation failures\n\n'
    for f in failures: summary+=f"- {f['title']} ({f['lane']}): {f['error']}\n"
summary+='\n## Digital-download candidate prepared\n\n- '+str(zip_path)+'\n- '+str(DIG_DIR/'etsy_listing_kit.md')+'\n\nDashboard: https://etsy.aetherisinnovations.com\n'
(PROD_DIR/'daily_factory_report.md').write_text(summary,encoding='utf-8')
print(json.dumps({'batch':BATCH,'qa':'PASS','created_count':len(created),'failure_count':len(failures),'failures':failures,'created':created,'report':str(PROD_DIR/'daily_factory_report.md'),'dashboard':'https://etsy.aetherisinnovations.com'},indent=2))
