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

ENV=Path('/root/.hermes/.env')
ROOT=Path('/root/etsy_printify_revenue_engine')
CSV=ROOT/'digital_products/strongest_themes_small_set_2026_05_17/etsy_upload_ready_master_completed_5.csv'
OUTDIR=ROOT/'digital_products/strongest_themes_small_set_2026_05_17/etsy_publish_run_2026_05_17'
OUTDIR.mkdir(parents=True,exist_ok=True)
OUT=OUTDIR/'publish_results.json'
REPORT=OUTDIR/'publish_report.md'
API='https://api.etsy.com/v3/application'

TAXONOMY_ID=1027  # copied from existing active digital printable wall art listing in this shop
RETURN_POLICY_ID=1

def load_env():
    d={}
    for raw in ENV.read_text(errors='ignore').splitlines():
        line=raw.strip()
        if line and not line.startswith('#') and '=' in line:
            k,v=line.split('=',1); d[k.strip()]=v.strip().strip('"').strip("'")
    return d

def headers(e, json_ct=False):
    api_key=e['ETSY_CLIENT_ID'] + (':' + e.get('ETSY_CLIENT_SECRET','') if e.get('ETSY_CLIENT_SECRET') else '')
    h={'x-api-key':api_key,'Authorization':f'Bearer {e["ETSY_ACCESS_TOKEN"]}'}
    if json_ct: h['Content-Type']='application/x-www-form-urlencoded'
    return h

def api_get(e, path, params=None):
    r=requests.get(API+path, headers=headers(e), params=params, timeout=90)
    try: data=r.json()
    except Exception: data={'raw':r.text[:1000]}
    return r.status_code,data

def api_post_form(e, path, data):
    r=requests.post(API+path, headers=headers(e), data=data, timeout=90)
    try: js=r.json()
    except Exception: js={'raw':r.text[:1000]}
    return r.status_code,js

def api_patch_form(e, path, data):
    r=requests.patch(API+path, headers=headers(e), data=data, timeout=90)
    try: js=r.json()
    except Exception: js={'raw':r.text[:1000]}
    return r.status_code,js

def api_upload_image(e, shop, listing_id, image_path):
    with open(image_path,'rb') as f:
        files={'image':(Path(image_path).name,f,'image/jpeg')}
        data={'rank':'1','overwrite':'true','alt_text':'Christian printable wall art digital download preview'}
        r=requests.post(f'{API}/shops/{shop}/listings/{listing_id}/images', headers=headers(e), data=data, files=files, timeout=180)
    try: js=r.json()
    except Exception: js={'raw':r.text[:1000]}
    return r.status_code,js

def api_upload_file(e, shop, listing_id, file_path, display_name):
    with open(file_path,'rb') as f:
        files={'file':(Path(file_path).name,f,'application/zip')}
        data={'name':display_name}
        r=requests.post(f'{API}/shops/{shop}/listings/{listing_id}/files', headers=headers(e), data=data, files=files, timeout=240)
    try: js=r.json()
    except Exception: js={'raw':r.text[:1000]}
    return r.status_code,js

def clean_desc(desc):
    return desc.strip().replace('\r\n','\n')[:5000]

def parse_desc(folder):
    md=(Path(folder)/'etsy_listing_kit.md').read_text(errors='ignore')
    marker='## Description\n'
    if marker in md:
        rest=md.split(marker,1)[1]
        desc=rest.split('\n## ',1)[0].strip()
    else:
        desc='DIGITAL DOWNLOAD — no physical item will be shipped.'
    return clean_desc(desc)

def existing_by_title(e, shop, title):
    # Check active and draft listings to avoid duplicate publishes if rerun.
    for state in ['active','draft']:
        off=0
        while True:
            code,data=api_get(e, f'/shops/{shop}/listings/{state}', {'limit':100,'offset':off})
            if code>=400: break
            for x in data.get('results') or []:
                if (x.get('title') or '').strip().lower()==title.strip().lower():
                    return x
            if len(data.get('results') or [])<100: break
            off+=100
    return None

def slug_from_title(title):
    return re.sub(r'[^a-z0-9]+','-',title.lower()).strip('-')[:60]

def save(rows): OUT.write_text(json.dumps(rows,indent=2),encoding='utf-8')

def render_report(rows):
    live=[r for r in rows if r.get('state')=='active' and r.get('url')]
    lines=[f'# Etsy Digital Publish Run — {datetime.now(timezone.utc).isoformat()}\n', f'- Rows processed: **{len(rows)}**', f'- Active verified URLs: **{len(live)}**\n', '## Listings']
    for i,r in enumerate(rows,1):
        lines.append(f'{i}. {r.get("title")} — state={r.get("state")} — listing `{r.get("listing_id")}` — {r.get("url","")}')
        if r.get('errors'):
            lines.append(f'   - Errors: `{json.dumps(r.get("errors"))[:500]}`')
    REPORT.write_text('\n'.join(lines)+'\n',encoding='utf-8')

def main():
    e=load_env(); shop=e['ETSY_SHOP_ID']
    rows=[]
    if OUT.exists():
        try: rows=json.loads(OUT.read_text())
        except Exception: rows=[]
    done_titles={r.get('title') for r in rows if r.get('state')=='active'}
    with CSV.open(newline='',encoding='utf-8') as f:
        records=list(csv.DictReader(f))
    for rec in records:
        title=rec['title'].strip()
        if title in done_titles: continue
        row={'title':title,'phrase':rec.get('phrase'),'started_at':datetime.now(timezone.utc).isoformat(),'errors':[]}
        try:
            existing=existing_by_title(e,shop,title)
            if existing:
                row['listing_id']=existing.get('listing_id'); row['state']=existing.get('state'); row['url']=existing.get('url'); row['existing']=True
                # still try to verify assets but don't mutate already active unless missing maybe
            else:
                desc=parse_desc(rec['folder'])
                tags=[t.strip() for t in rec['tags'].split('|') if t.strip()][:13]
                data={
                    'quantity':'999',
                    'title':title,
                    'description':desc,
                    'price':rec.get('price') or '7.99',
                    'who_made':'i_did',
                    'when_made':'2020_2026',
                    'taxonomy_id':str(TAXONOMY_ID),
                    'type':'download',
                    'listing_type':'download',
                    'is_supply':'false',
                    'should_auto_renew':'false',
                    'tags':','.join(tags),
                    'materials':'digital download,printable wall art,jpg,pdf,zip file',
                    'return_policy_id':str(RETURN_POLICY_ID),
                }
                code,js=api_post_form(e, f'/shops/{shop}/listings', data)
                row['create_status']=code; row['create_response']=js
                if code>=400 or not js.get('listing_id'):
                    # retry without listing_type if API disliked duplicate field
                    row['errors'].append({'create':js})
                    data.pop('listing_type',None)
                    code,js=api_post_form(e, f'/shops/{shop}/listings', data)
                    row['create_retry_status']=code; row['create_retry_response']=js
                if code>=400 or not js.get('listing_id'):
                    row['state']='failed_create'; row['errors'].append({'create_retry':js}); rows.append(row); save(rows); render_report(rows); continue
                row['listing_id']=js['listing_id']; row['state']=js.get('state','draft'); row['url']=js.get('url')
            lid=row['listing_id']
            # Upload hero image first.
            code,img=api_upload_image(e,shop,lid,rec['preview'])
            row['image_upload_status']=code; row['image_upload_response']=img
            if code>=400: row['errors'].append({'image':img})
            # Upload digital ZIP.
            display=f"{slug_from_title(rec.get('phrase') or title)}-printable-wall-art-bundle.zip"
            code,filejs=api_upload_file(e,shop,lid,rec['zip'],display)
            row['file_upload_status']=code; row['file_upload_response']=filejs
            if code>=400: row['errors'].append({'file':filejs})
            # Activate/publish listing after assets. If already active, patch will simply keep active.
            code,act=api_patch_form(e, f'/shops/{shop}/listings/{lid}', {'state':'active'})
            row['activate_status']=code; row['activate_response']=act
            if code>=400: row['errors'].append({'activate':act})
            # Read back to verify
            time.sleep(2)
            code,listing=api_get(e, f'/listings/{lid}')
            row['verify_status']=code
            row['state']=listing.get('state')
            row['url']=listing.get('url') or row.get('url')
            row['listing_id']=lid
            # Verify files endpoint
            code,files=api_get(e, f'/shops/{shop}/listings/{lid}/files')
            row['files_status']=code; row['files_count']=files.get('count')
            code,images=api_get(e, f'/listings/{lid}/images')
            row['images_status']=code; row['images_count']=images.get('count')
        except Exception as ex:
            row['state']='exception'; row['errors'].append({'exception':repr(ex)})
        row['completed_at']=datetime.now(timezone.utc).isoformat()
        rows.append(row); save(rows); render_report(rows)
        print(json.dumps({'title':title,'listing_id':row.get('listing_id'),'state':row.get('state'),'url':row.get('url'),'errors':len(row.get('errors') or [])},ensure_ascii=False),flush=True)
    render_report(rows)
    print(json.dumps({'results':str(OUT),'report':str(REPORT),'active':sum(1 for r in rows if r.get('state')=='active'),'rows':len(rows)},indent=2))

if __name__=='__main__': main()
