"""Server-side OpenAI integration. No simulated AI output."""
import os, json, re, base64
import httpx

API_BASE = os.getenv('OPENAI_BASE_URL', 'https://api.openai.com/v1').rstrip('/')
TEXT_MODEL = os.getenv('OPENAI_TEXT_MODEL', 'gpt-4.1-mini')
IMAGE_MODEL = os.getenv('OPENAI_IMAGE_MODEL', 'gpt-image-1.5')

class ProviderError(Exception):
    pass

def configured():
    return bool(os.getenv('OPENAI_API_KEY'))

def _post(route, payload, timeout=120):
    key = os.getenv('OPENAI_API_KEY')
    if not key:
        raise ProviderError('OPENAI_API_KEY belum dikonfigurasi di server.')
    try:
        with httpx.Client(timeout=timeout) as client:
            resp=client.post(f'{API_BASE}/{route}', json=payload, headers={'Authorization':f'Bearer {key}', 'Content-Type':'application/json'})
            resp.raise_for_status()
            return resp.json()
    except httpx.HTTPStatusError as e:
        # Don't leak API keys or full request bodies into front-end.
        message = e.response.text[:360]
        raise ProviderError(f'Provider AI menolak permintaan ({e.response.status_code}): {message}') from e
    except (httpx.RequestError, ValueError) as e:
        raise ProviderError(f'Koneksi API AI gagal: {type(e).__name__}') from e

def _output_text(data):
    texts=[]
    for item in data.get('output', []):
        for content in item.get('content', []):
            if content.get('type')=='output_text':
                texts.append(content.get('text',''))
    if not texts: raise ProviderError('AI tidak mengembalikan teks.')
    return '\n'.join(texts)

def json_prompt(prompt, *, image_bytes=None, mime='image/jpeg'):
    content=[{'type':'input_text','text':prompt+'\nBalas JSON valid saja, tanpa markdown.'}]
    if image_bytes:
        content.append({'type':'input_image','image_url':f'data:{mime};base64,{base64.b64encode(image_bytes).decode()}'})
    payload={'model':TEXT_MODEL,'input':[{'role':'user','content':content}]}
    value=_output_text(_post('responses',payload, timeout=180)).strip()
    try:return json.loads(value)
    except json.JSONDecodeError:
        m=re.search(r'\{[\s\S]*\}',value)
        if m:
            try: return json.loads(m.group())
            except ValueError: pass
        raise ProviderError('AI mengembalikan JSON tidak valid. Silakan ulangi.')

def analyze_reference(image_bytes, mime='image/jpeg'):
    return json_prompt('Analisis gaya DESAIN Instagram yang diunggah. Kembalikan JSON: {"visual_style": string pendek, "composition": string pendek, "typography": string pendek, "illustration": string pendek, "layout_notes": string pendek}. Fokus pada tampilan, tidak menyalin teks yang terlihat.', image_bytes=image_bytes, mime=mime)

def generate_plan(brand, theme, count, earlier_titles=None):
    """Makes small chunks to avoid truncated 90-item JSON outputs."""
    earlier_titles=earlier_titles or []
    result=[]
    for start in range(0,count,10):
        n=min(10,count-start)
        excluded=earlier_titles[-120:] + [x['title'] for x in result]
        prompt=f'''Kamu creative director Instagram Indonesia. Buat TEPAT {n} ide konten BERBEDA.
Brand: {brand['name']}. Industri: {brand.get('industry','')}. Audience: {brand.get('audience','')}.
Tema permintaan: {theme or 'edukasi, engagement, hiburan dan promosi yang bervariasi'}.
JANGAN ulang ide/topik ini: {json.dumps(excluded[-120:],ensure_ascii=False)}.
Variasikan kategori, pesan, angle, dan visual; hindari klaim fakta spesifik yang tidak pasti.
Kembalikan JSON OBJECT dengan key "items" berisi array TEPAT {n} objects,
masing-masing keys: "title" (maksimal 65 karakter, headline menggugah),
"subtitle" (maksimal 125 karakter, informasi singkat),
"category" (satu kata: edukasi/engagement/promosi/hiburan/storytelling),
"visual_prompt" (visual illustration/art direction berbeda untuk tiap konten, bahasa Inggris),
"caption_body" (caption 2-4 kalimat dalam bahasa Indonesia, sesuai judul, jangan pakai hashtag atau CTA awal brand),
"cta" (pertanyaan atau ajakan singkat dalam bahasa Indonesia).'''
        data=json_prompt(prompt)
        items=data.get('items')
        if not isinstance(items,list) or len(items)!=n:
            raise ProviderError(f'AI menghasilkan {len(items) if isinstance(items,list) else 0} ide, bukan {n}.')
        for item in items:
            if not all(isinstance(item.get(k),str) and item[k].strip() for k in ['title','subtitle','category','visual_prompt','caption_body','cta']):
                raise ProviderError('Salah satu ide AI tidak lengkap.')
        result.extend(items)
    return result

def generate_visual(prompt, brand, style, mode='medium'):
    style_note = style.get('notes','') if isinstance(style,dict) else ''
    prompt=f'''Create a premium original vertical editorial illustration/art-directed photo for an Instagram campaign.
Brand: {brand['name']}. Brand colors: {brand.get('primary','#7C3AED')}, {brand.get('secondary','#FFF8F2')}.
Style reference extracted notes: {style_note}. Style mode: {mode}.
Subject and composition: {prompt}.
Output a richly detailed original scene with artistic depth, subject situated mainly in the center/upper portion.
IMPORTANT: Absolutely NO text, letters, typography, logos, watermarks, borders or Instagram UI. The design renderer will add typography separately.'''
    payload={'model':IMAGE_MODEL,'prompt':prompt,'size':'1024x1536','quality':'medium','output_format':'png','n':1}
    data=_post('images/generations',payload,timeout=240)
    try:
        return base64.b64decode(data['data'][0]['b64_json'], validate=True)
    except (KeyError,IndexError,ValueError) as e:
        raise ProviderError('Image AI tidak mengembalikan data gambar base64.') from e
