#!/usr/bin/env python3
from __future__ import annotations

import csv
import json
import os
import re
import 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')
SHIRT_REPORT = ROOT / 'printify_draft_products' / 'printify_draft_creation_report.csv'
VERIFIED_SHIRTS = ROOT / 'printify_draft_products' / 'printify_accepted50_drafts_verified.csv'
OUT_DIR = ROOT / 'printify_nonshirt_product_expansion'
OUT_DIR.mkdir(parents=True, exist_ok=True)

ENV = Path('/root/.hermes/.env')
for line in ENV.read_text().splitlines():
    line = line.strip()
    if not line or line.startswith('#') or '=' not in line:
        continue
    k, v = line.split('=', 1)
    os.environ.setdefault(k.strip(), v.strip().strip('"').strip("'"))

TOKEN = os.environ.get('PRINTIFY_API_TOKEN')
SHOP_ID = 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'}

LANES = [
    {
        'lane': 'hoodie',
        'blueprint_id': 77,
        'provider_id': 99,
        'title_suffix': 'Christian Hoodie | Vintage Faith Sweatshirt',
        'desc_product': 'premium Christian hoodie',
        'colors': ['Black', 'Navy', 'Dark Heather', 'Maroon', 'Forest Green'],
        'sizes': ['S', 'M', 'L', 'XL', '2XL'],
        'base_price': 4499,
        'plus_price': 4699,
        'position': 'front',
        'scale': 0.86,
        'y': 0.50,
    },
    {
        'lane': 'sweatshirt',
        'blueprint_id': 49,
        'provider_id': 99,
        'title_suffix': 'Christian Sweatshirt | Quiet Faith Crewneck',
        'desc_product': 'premium Christian crewneck sweatshirt',
        'colors': ['Black', 'Navy', 'Dark Heather', 'Charcoal', 'Maroon'],
        'sizes': ['S', 'M', 'L', 'XL', '2XL'],
        'base_price': 3999,
        'plus_price': 4199,
        'position': 'front',
        'scale': 0.88,
        'y': 0.49,
    },
    {
        'lane': 'white_mug_11oz',
        'blueprint_id': 68,
        'provider_id': 1,
        'title_suffix': 'Christian Mug | Faith Coffee Cup',
        'desc_product': 'Christian coffee mug',
        'variant_ids': [33719],
        'price': 1699,
        'position': 'front',
        # 2700x1120 mug wrap: square artwork crops vertically above ~0.414.
        # Use 0.38 so the full badge/text stays inside the safe buyer-facing mug area.
        'scale': 0.38,
        'y': 0.50,
    },
    {
        'lane': 'black_mug_15oz',
        'blueprint_id': 425,
        'provider_id': 1,
        'title_suffix': 'Black Christian Mug | Faith Coffee Cup',
        'desc_product': 'black Christian coffee mug',
        'variant_ids': [62014],
        'price': 1899,
        'position': 'front',
        # 2790x1219 mug wrap: square artwork crops vertically above ~0.437.
        # Use 0.40 so the full artwork is visible with a little print margin.
        'scale': 0.40,
        'y': 0.50,
    },
    {
        'lane': 'poster',
        'blueprint_id': 282,
        'provider_id': 99,
        'title_suffix': 'Christian Poster | Faith Wall Art Print',
        'desc_product': 'Christian wall art poster',
        # Keep the most common giftable sizes rather than every obscure poster size.
        'variant_ids': [114557, 43135, 43138, 43141, 43144, 43147],
        'price_by_variant': {114557: 1299, 43135: 1499, 43138: 1899, 43141: 2299, 43144: 2699, 43147: 2999},
        'position': 'front',
        'scale': 0.94,
        'y': 0.50,
    },
    {
        'lane': 'notebook',
        'blueprint_id': 74,
        'provider_id': 1,
        'title_suffix': 'Christian Notebook | Prayer Journal',
        'desc_product': 'Christian notebook and prayer journal',
        'variant_ids': [34240],
        'price': 1899,
        'position': 'front',
        'scale': 0.90,
        'y': 0.50,
    },
]


def api(method: str, url: str, **kwargs):
    for attempt in range(1, 4):
        try:
            r = requests.request(method, url, headers=HEADERS, timeout=180, **kwargs)
            if r.status_code in (429, 500, 502, 503, 504) and attempt < 3:
                time.sleep(5 * attempt)
                continue
            return r
        except requests.RequestException:
            if attempt == 3:
                raise
            time.sleep(5 * attempt)


def phrase_from_file(path: str) -> str:
    stem = Path(path).stem
    stem = re.sub(r'^QF50-\d+_', '', stem)
    stem = re.sub(r'_v\d+_', '_', stem)
    stem = stem.replace('_ORIGINAL_UNTOUCHED', '')
    return ' '.join(w.capitalize() for w in stem.split('_') if w)


def slug_phrase(phrase: str) -> str:
    return re.sub(r'[^a-z0-9]+', '-', phrase.lower()).strip('-')


def clean_title(phrase: str, suffix: str) -> str:
    title = f'{phrase} {suffix}'
    return title[:140]


def tags_for(phrase: str, lane: str):
    base = [
        'christian gift', 'faith gift', 'quiet faith', 'gracedprintco',
        'christian woman', 'christian men', 'jesus gift', 'prayer gift'
    ]
    phrase_tag = phrase.lower()[:20]
    lane_tags = {
        'hoodie': ['christian hoodie', 'faith hoodie', 'jesus hoodie', 'church hoodie', 'prayer hoodie'],
        'sweatshirt': ['christian sweatshirt', 'faith sweatshirt', 'jesus sweatshirt', 'church sweatshirt', 'cozy faith'],
        'white_mug_11oz': ['christian mug', 'faith mug', 'coffee cup', 'prayer mug', 'jesus mug'],
        'black_mug_15oz': ['black mug', 'christian mug', 'faith mug', 'coffee cup', 'jesus mug'],
        'poster': ['christian poster', 'faith wall art', 'jesus wall art', 'christian decor', 'prayer room decor'],
        'notebook': ['christian notebook', 'prayer journal', 'faith journal', 'bible notes', 'church notebook'],
    }[lane]
    tags = [phrase_tag] + lane_tags + base
    out = []
    for t in tags:
        t = t[:20]
        if t and t not in out:
            out.append(t)
    return out[:13]


def description(phrase: str, lane: dict):
    return (
        f'{phrase} {lane["desc_product"]} from the Quiet Faith Collection.\n\n'
        'Original premium faith artwork with a vintage, peaceful Christian aesthetic. '
        'Created for believers who want encouragement that feels calm, strong, and intentional. '
        'The approved artwork master is preserved unchanged; Printify generates the product mockups and fulfillment.\n\n'
        'Great as a Christian gift, prayer-room item, church group gift, encouragement gift, or everyday faith reminder.\n\n'
        'Draft created through GracedPrintCo product-family expansion. Product is not published to Etsy until separately pushed/verified.'
    )


def load_existing_titles():
    existing = set()
    page = 1
    while True:
        r = api('GET', f'{BASE}/shops/{SHOP_ID}/products.json?page={page}&limit=50')
        if r.status_code >= 300:
            raise SystemExit(f'Could not load Printify products page {page}: {r.status_code} {r.text[:500]}')
        data = r.json()
        for p in data.get('data', []):
            if p.get('title'):
                existing.add(p['title'].strip().lower())
        if page >= int(data.get('last_page') or page):
            break
        page += 1
    return existing


def variant_map(bp: int, pp: int):
    r = api('GET', f'{BASE}/catalog/blueprints/{bp}/print_providers/{pp}/variants.json')
    if r.status_code >= 300:
        raise SystemExit(f'Variant lookup failed {bp}/{pp}: {r.status_code} {r.text[:500]}')
    variants = r.json().get('variants', [])
    out = {}
    for v in variants:
        opts = v.get('options') or {}
        out[(opts.get('color'), opts.get('size'))] = v['id']
    return out


def build_variants(lane: dict, vmap_cache: dict):
    if 'variant_ids' in lane:
        if 'price_by_variant' in lane:
            return [{'id': vid, 'price': lane['price_by_variant'].get(vid, lane.get('price', 1999)), 'is_enabled': True} for vid in lane['variant_ids']]
        return [{'id': vid, 'price': lane['price'], 'is_enabled': True} for vid in lane['variant_ids']]
    key = (lane['blueprint_id'], lane['provider_id'])
    if key not in vmap_cache:
        vmap_cache[key] = variant_map(*key)
    vmap = vmap_cache[key]
    variants = []
    for color in lane['colors']:
        for size in lane['sizes']:
            vid = vmap.get((color, size))
            if vid:
                price = lane['plus_price'] if size in ('2XL', '3XL', '4XL', '5XL') else lane['base_price']
                variants.append({'id': vid, 'price': price, 'is_enabled': True})
    return variants


def extract_first_image_id(product: dict) -> str | None:
    for area in product.get('print_areas') or []:
        for ph in area.get('placeholders') or []:
            for img in ph.get('images') or []:
                if img.get('id'):
                    return img['id']
    return None


def fetch_product_image_id(product_id: str) -> str | None:
    r = api('GET', f'{BASE}/shops/{SHOP_ID}/products/{product_id}.json')
    if r.status_code >= 300:
        print(f'WARNING: could not fetch product {product_id} for image id: {r.status_code} {r.text[:200]}', flush=True)
        return None
    return extract_first_image_id(r.json())


def main():
    # The creation report may only contain the tail of a prior run. The verified report has all 50
    # product IDs; fetch each shirt's existing uploaded image ID from Printify so we can reuse the
    # approved master art without re-uploading/resizing/cropping it.
    verified_rows = list(csv.DictReader(open(VERIFIED_SHIRTS)))
    creation_rows = {r.get('product_id'): r for r in csv.DictReader(open(SHIRT_REPORT))} if SHIRT_REPORT.exists() else {}
    shirt_rows = []
    for r in verified_rows:
        pid = r.get('product_id')
        image_id = (creation_rows.get(pid) or {}).get('image_id')
        if not image_id and pid:
            image_id = fetch_product_image_id(pid)
            time.sleep(0.15)
        rr = dict(r)
        rr['image_id'] = image_id or ''
        shirt_rows.append(rr)
    shirt_rows = [r for r in shirt_rows if r.get('image_id')]
    if len(shirt_rows) != 50:
        print(f'WARNING: expected 50 shirt rows with reusable image IDs, got {len(shirt_rows)}')

    existing_titles = load_existing_titles()
    vmap_cache = {}
    created = []
    skipped = []
    errors = []
    payload_log = []

    total = len(shirt_rows) * len(LANES)
    n = 0
    for row in shirt_rows:
        phrase = row.get('phrase') or phrase_from_file(row.get('file') or row.get('accepted_master') or '')
        image_id = row['image_id']
        for lane in LANES:
            n += 1
            title = clean_title(phrase, lane['title_suffix'])
            if title.strip().lower() in existing_titles:
                skipped.append({'phrase': phrase, 'lane': lane['lane'], 'title': title, 'reason': 'title_already_exists'})
                print(f'{n:03d}/{total} SKIP existing {lane["lane"]}: {title}', flush=True)
                continue
            variants = build_variants(lane, vmap_cache)
            if not variants:
                errors.append({'phrase': phrase, 'lane': lane['lane'], 'title': title, 'error': 'no variants selected'})
                print(f'{n:03d}/{total} ERROR no variants {lane["lane"]}: {title}', flush=True)
                continue
            variant_ids = [v['id'] for v in variants]
            body = {
                'title': title,
                'description': description(phrase, lane),
                'blueprint_id': lane['blueprint_id'],
                'print_provider_id': lane['provider_id'],
                'variants': variants,
                'print_areas': [{
                    'variant_ids': variant_ids,
                    'placeholders': [{
                        'position': lane['position'],
                        'images': [{
                            'id': image_id,
                            'x': 0.5,
                            'y': lane['y'],
                            'scale': lane['scale'],
                            'angle': 0,
                        }]
                    }]
                }],
                'tags': tags_for(phrase, lane['lane']),
            }
            r = api('POST', f'{BASE}/shops/{SHOP_ID}/products.json', json=body)
            payload_log.append({'phrase': phrase, 'lane': lane['lane'], 'title': title, 'status_code': r.status_code, 'response': r.text[:1500], 'payload': body})
            if r.status_code >= 300:
                errors.append({'phrase': phrase, 'lane': lane['lane'], 'title': title, 'status_code': r.status_code, 'error': r.text[:1000]})
                print(f'{n:03d}/{total} ERROR create {lane["lane"]}: {title} -> {r.status_code} {r.text[:120]}', flush=True)
                time.sleep(1)
                continue
            pj = r.json()
            created.append({
                'phrase': phrase,
                'lane': lane['lane'],
                'title': pj.get('title', title),
                'printify_product_id': pj.get('id'),
                'visible': pj.get('visible'),
                'external': pj.get('external'),
                'blueprint_id': lane['blueprint_id'],
                'print_provider_id': lane['provider_id'],
                'image_id': image_id,
                'variant_count': len(variants),
                'created_at': datetime.now(timezone.utc).isoformat(),
                'status': 'draft_created_not_published',
            })
            existing_titles.add(title.strip().lower())
            print(f'{n:03d}/{total} CREATED {lane["lane"]}: {pj.get("id")} {title}', flush=True)
            time.sleep(0.45)

    (OUT_DIR / 'created_nonshirt_products.json').write_text(json.dumps(created, indent=2))
    (OUT_DIR / 'skipped_existing_nonshirt_products.json').write_text(json.dumps(skipped, indent=2))
    (OUT_DIR / 'errors_nonshirt_products.json').write_text(json.dumps(errors, indent=2))
    (OUT_DIR / 'payloads_and_responses.json').write_text(json.dumps(payload_log, indent=2))

    csv_path = OUT_DIR / 'created_nonshirt_products.csv'
    with csv_path.open('w', newline='') as f:
        fields = ['phrase', 'lane', 'title', 'printify_product_id', 'visible', 'external', 'blueprint_id', 'print_provider_id', 'image_id', 'variant_count', 'status', 'created_at']
        w = csv.DictWriter(f, fieldnames=fields)
        w.writeheader()
        for r in created:
            w.writerow({k: r.get(k) for k in fields})

    counts = {}
    for r in created:
        counts[r['lane']] = counts.get(r['lane'], 0) + 1
    summary = {
        'created': len(created),
        'skipped': len(skipped),
        'errors': len(errors),
        'created_by_lane': counts,
        'out_dir': str(OUT_DIR),
        'note': 'These are Printify draft products only. They are not pushed/published to Etsy yet, so no Etsy listing-fee publish step was executed.',
    }
    (OUT_DIR / 'summary.json').write_text(json.dumps(summary, indent=2))

    md = ['# Premium 50 Non-Shirt Product Expansion', '', f'Created: {len(created)} Printify draft products', f'Skipped existing: {len(skipped)}', f'Errors: {len(errors)}', '', 'These are Printify drafts only; Etsy publication/listing-fee step was not run.', '', '## Created by lane']
    for lane, count in sorted(counts.items()):
        md.append(f'- {lane}: {count}')
    md += ['', '## Files', f'- CSV: `{csv_path}`', f'- JSON: `{OUT_DIR / "created_nonshirt_products.json"}`', f'- Errors: `{OUT_DIR / "errors_nonshirt_products.json"}`']
    (OUT_DIR / 'report.md').write_text('\n'.join(md) + '\n')

    print(json.dumps(summary, indent=2))

if __name__ == '__main__':
    main()
