#!/usr/bin/env python3
import os, json, time, base64, mimetypes, re
from pathlib import Path
import requests
from PIL import Image, ImageDraw, ImageFont

BASE='https://mcp.mxai.cn'
KEY=os.environ.get('MX_AI_API_KEY')
if not KEY:
    raise SystemExit('MX_AI_API_KEY not set')
HEADERS={'Authorization': f'Bearer {KEY}', 'Content-Type': 'application/json'}
PROJECT=Path('/Users/bot1/Volumes/root_for_ai/AI工作区/国博_产品_陶鹰鼎小鹰包挂效果图_20260620_220637')
OUT=PROJECT/'deliverables'
WORK=PROJECT/'work'
OUT.mkdir(parents=True, exist_ok=True)
WORK.mkdir(parents=True, exist_ok=True)
SRC=Path('/Users/bot1/Volumes/root_for_ai/AI工作区/国博_产品_国宝艺展毛绒盲盒_20260620_2052/deliverables/单个盲盒_白底产品图/06_棕色小鸟挂件_白底产品图.png')
META=PROJECT/'mxai_tasks_manifest.json'

mime=mimetypes.guess_type(str(SRC))[0] or 'image/png'
b64=base64.b64encode(SRC.read_bytes()).decode('ascii')
data_url=f'data:{mime};base64,{b64}'

product_fidelity = (
    '以参考图中的国宝艺展毛绒盲盒“陶鹰鼎”褐色小鹰挂件为唯一产品母图；只能优化真实摄影光影、阴影、环境反光与融合感，绝对不能改变产品样式。'
    '必须保留：奶油浅棕色毛绒圆润小鹰身体、两侧中褐色翅膀、两个黑色亮面珠眼、粉色腮红刺绣、深褐色椭圆小嘴、下方两只圆润脚部略带浅绿色阴影、顶部芥黄色织唛和白色刺绣标识、深紫褐色圆形塑料扣环。'
    '产品比例是小巧包挂约8-10cm，挂在包身上约为包高1/5到1/4。'
    '扣环必须真实受力：包自身肩带根部、侧边缝制布袢或帆布带必须直接穿过这个深紫褐色圆环，圆环完整圈住承重点，毛绒自然垂下；不要额外生成长绳、金属链、第二个环、假挂点或悬浮粘贴。'
    '保留产品上已有刺绣和织唛，不新增文字、数字、Logo、水印、标签文案或额外品牌信息。'
)
base_style = (
    '商业摄影级写实效果图，明快清新、可爱但不廉价，年轻女生/学生喜欢的帆布小包。'
    '自然日光、柔和高光、真实接触阴影、浅景深，产品毛绒边缘清晰，包料有帆布织物质感。'
    '画面不需要任何标题或排版文字。'
)

jobs = [
    ('scene_01_奶油黄小花帆布包', '近景静物场景图，无模特。奶油黄色小号帆布单肩包，肩带根部有真实缝制布袢，包面有很细小的白色雏菊点缀，背景为夏日明亮书桌和柔和窗光。'),
    ('scene_02_浅天蓝学院风帆布包', '近景静物场景图，无模特。浅天蓝色小号帆布斜挎包，圆润翻盖，可爱的学院风，肩带根部真实承重点穿过圆环，背景为清爽校园木桌、虚化绿植。'),
    ('scene_03_米白橘黄撞色帆布包', '近景静物场景图，无模特。米白帆布小包配橘黄色包边和短肩带，活泼明快，挂件扣在侧边缝制帆布袢上，背景为明亮咖啡店窗边桌面。'),
    ('scene_04_薄荷绿雏菊帆布小包', '近景静物场景图，无模特。薄荷绿色轻薄帆布小包，少量小雏菊刺绣装饰，包型精致不臃肿，挂件扣在肩带根部，背景为夏日浅色木椅和阳光。'),
    ('scene_05_淡粉米白可爱帆布包', '近景静物场景图，无模特。淡粉色与米白拼色的小号帆布腋下包，俏皮可爱、质感干净，挂件扣在侧边短布袢上，背景为明亮卧室梳妆台一角，柔和空气感。'),
    ('model_01_奶油黄帆布包侧脸氛围', '带模特示意图。年轻女生只露侧脸和肩颈氛围，不看镜头；穿浅色夏季短袖，背奶油黄色小号帆布单肩包，陶鹰鼎小鹰挂件扣在肩带根部，挂件清晰可见。'),
    ('model_02_浅蓝帆布包校园侧脸', '带模特示意图。年轻女生校园户外侧脸氛围，不看镜头，浅蓝色帆布斜挎小包，包带从肩部自然下垂，挂件圆环直接套在包带根部布袢上，阳光明快。'),
    ('model_03_米白橘黄帆布包街拍侧脸', '带模特示意图。年轻女生街拍侧脸，只露半侧脸、发丝和上半身，米白橘黄撞色帆布小包斜背，挂件小巧自然垂挂，画面活泼明亮。'),
    ('model_04_薄荷绿帆布包夏日侧脸', '带模特示意图。年轻女生夏日侧脸氛围，浅色短袖，背薄荷绿色雏菊帆布小包，包款轻薄精致，陶鹰鼎小鹰挂件扣在肩带根部真实受力，背景虚化绿植。'),
    ('model_05_淡粉帆布包温柔侧脸', '带模特示意图。年轻女生温柔侧脸氛围，只露侧脸轮廓和肩膀，淡粉米白拼色帆布小包，挂件扣在侧边缝制布袢上，柔和自然光，少女感但高级。'),
]

prompt_suffix = (
    '负面要求：不要改变小鹰毛绒的颜色、脸、翅膀、脚、织唛、圆环形状；不要把小鹰变成真实鸟、陶器、卡通重绘或其他动物；不要让圆环漂浮、贴在包面中间、悬空或没有受力关系；不要多余长绳、金属链、钥匙扣链、第二个环；不要文字、数字、Logo、水印；不要廉价粗布袋、厚嘟嘟棉包、黑色暗包、杂乱背景。'
)

def load_manifest():
    if META.exists():
        return json.loads(META.read_text())
    return {'source': str(SRC), 'jobs': []}

def save_manifest(m):
    META.write_text(json.dumps(m, ensure_ascii=False, indent=2))

def post_json(path, payload, timeout=60):
    r=requests.post(BASE+path, headers=HEADERS, json=payload, timeout=timeout)
    if r.status_code>=400:
        raise RuntimeError(f'HTTP {r.status_code} {r.text}')
    return r.json()

def get_json(path, timeout=60):
    r=requests.get(BASE+path, headers={'Authorization': f'Bearer {KEY}'}, timeout=timeout)
    if r.status_code>=400:
        raise RuntimeError(f'HTTP {r.status_code} {r.text}')
    return r.json()

def download(url, path):
    r=requests.get(url, timeout=120)
    r.raise_for_status()
    path.write_bytes(r.content)

manifest=load_manifest()
existing={j['name']:j for j in manifest.get('jobs', [])}
# submit all missing tasks
for name, scene in jobs:
    if name in existing and existing[name].get('serial_no'):
        continue
    prompt = product_fidelity + base_style + scene + prompt_suffix
    payload={
        'prompt': prompt,
        'model': 'gpt-image-2',
        'aspect_ratio': '3:4',
        'resolution': '2K',
        'quality': 'high',
        'count': 1,
        'input_images': [data_url],
    }
    print('SUBMIT', name, flush=True)
    res=post_json('/mcp/api/generate/image', payload, timeout=120)
    item={'name': name, 'prompt': prompt, 'payload_summary': {k: payload[k] for k in ['model','aspect_ratio','resolution','quality','count']}, 'serial_no': res.get('serial_no'), 'submit_response': res, 'status': 'submitted'}
    manifest.setdefault('jobs', []).append(item)
    existing[name]=item
    save_manifest(manifest)
    time.sleep(2)

# poll until completion/fail
pending=True
while pending:
    pending=False
    for item in manifest['jobs']:
        if item.get('status') in ('completed','failed'):
            continue
        sn=item.get('serial_no')
        if not sn:
            item['status']='failed'; item['fail_msg']='missing serial_no'; continue
        try:
            st=get_json(f'/mcp/api/task/{sn}', timeout=90)
        except Exception as e:
            print('POLL_ERR', item['name'], e, flush=True)
            pending=True
            continue
        item['last_status_response']=st
        status=st.get('status')
        print('POLL', item['name'], sn, status, st.get('status_text'), flush=True)
        if status==2:
            urls=st.get('image_urls') or []
            item['image_urls']=urls
            item['files']=[]
            for idx,url in enumerate(urls, start=1):
                ext='.png'
                # keep stable png name regardless remote encoding
                out=OUT/f"{item['name']}.png" if len(urls)==1 else OUT/f"{item['name']}_{idx}.png"
                download(url,out)
                item['files'].append(str(out))
            item['status']='completed'
        elif status==3:
            item['status']='failed'
            item['fail_msg']=st.get('fail_msg') or st.get('message') or 'failed'
        else:
            pending=True
        save_manifest(manifest)
        time.sleep(1)
    if pending:
        time.sleep(8)

# contact sheets
font=None
try:
    font=ImageFont.truetype('/System/Library/Fonts/PingFang.ttc', 28)
except Exception:
    font=ImageFont.load_default()

def make_sheet(items, filename, title):
    thumbs=[]
    for item in items:
        if item.get('status')!='completed' or not item.get('files'):
            continue
        img=Image.open(item['files'][0]).convert('RGB')
        img.thumbnail((420,560), Image.LANCZOS)
        tile=Image.new('RGB',(460,640),'white')
        tile.paste(img,((460-img.width)//2,20))
        d=ImageDraw.Draw(tile)
        d.text((20,590),item['name'],fill=(40,40,40),font=font)
        thumbs.append(tile)
    if not thumbs: return
    cols=5
    rows=(len(thumbs)+cols-1)//cols
    sheet=Image.new('RGB',(cols*460, rows*640+70),(245,245,242))
    d=ImageDraw.Draw(sheet)
    d.text((30,20),title,fill=(30,30,30),font=font)
    for i,t in enumerate(thumbs):
        x=(i%cols)*460; y=70+(i//cols)*640
        sheet.paste(t,(x,y))
    sheet.save(OUT/filename, quality=95)

scene_items=[i for i in manifest['jobs'] if i['name'].startswith('scene_')]
model_items=[i for i in manifest['jobs'] if i['name'].startswith('model_')]
make_sheet(scene_items,'00_陶鹰鼎小鹰_场景图5张总览.jpg','陶鹰鼎褐色小鹰包挂效果图｜场景图5张')
make_sheet(model_items,'00_陶鹰鼎小鹰_模特图5张总览.jpg','陶鹰鼎褐色小鹰包挂效果图｜模特图5张')
# simple zip
import zipfile
zip_path=PROJECT/'国宝艺展_陶鹰鼎褐色小鹰_包挂场景模特效果图_交付包.zip'
with zipfile.ZipFile(zip_path,'w',zipfile.ZIP_DEFLATED) as z:
    z.write(META, META.name)
    for p in sorted(OUT.glob('*')):
        z.write(p, 'deliverables/'+p.name)
print('DONE')
print('PROJECT', PROJECT)
print('ZIP', zip_path)
for item in manifest['jobs']:
    print(item['name'], item.get('status'), item.get('files', [''])[0] if item.get('files') else item.get('fail_msg',''))
