#!/usr/bin/env python3
"""
Runner Harian Artikel Web (Solusi B)
====================================
Setiap hari (via cron jam 09:00 WIB) mengambil 1 artikel BELUM-TERPAKAI per
website dari pool `stok_artikel_web.json`, lalu publish schedule ke WordPress
dengan Yoast SEO + featured image Pexels + WA button.

ANTI-DUPLIKAT (3 lapis):
1. Tracker file `artikel_used_tracker.json` — catat slug sudah dipakai per website.
2. Cek WP langsung — skip kalau website sudah punya post (publish/future/draft) utk tanggal sasaran.
3. Dedup hash isi — skip kalau konten sama dengan post yang sudah ada di website.

Config kredensial dibaca dari `cfg_<client>.py` yang SOURCE OF TRUTH (user/pass/domain).

Cara jalan: python3 publish_harian_web.py [--date YYYY-MM-DD] [--client X]
(Default: tanggal besok jam 09:00 WIB, semua client)
"""
import os, sys, json, glob, importlib.util, base64, urllib.request, urllib.parse, hashlib, re, datetime

WS = os.path.dirname(os.path.abspath(__file__))
TRACKER = os.path.join(WS, 'artikel_used_tracker.json')
POOL = os.path.join(WS, 'stok_artikel_web.json')
LOG = os.path.join(WS, 'log_pipeline_harian.jsonl')
PEXELS_KEY = '/home/mantapman/.openrouter_key'  # placeholder; key pexels ada di file lain

# ------------------------------------------------------------ creds dari cfg_*.py
def load_cfg(path):
    spec = importlib.util.spec_from_file_location('c', path)
    m = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(m)
    return m.CFG

def get_creds_for_client(client):
    """cari cfg_<client>.py (case-insensitive, tanpa _update)."""
    want = client.lower().replace(' ','')
    cands = [f for f in glob.glob(os.path.join(WS,'cfg_*.py')) if '_update' not in f]
    for f in cands:
        base = os.path.basename(f)[4:-3].lower().replace(' ','')
        if base == want or base.startswith(want[:4]):
            try: return load_cfg(f)
            except Exception: pass
    # fallback label match
    for f in cands:
        try:
            c = load_cfg(f)
            if c.get('label','').lower().replace(' ','') == want:
                return c
        except Exception: pass
    return None

# ------------------------------------------------------------ WP API
def wp_req(domain, user, pw, ep, method='GET', data=None, params=None):
    auth = base64.b64encode(f"{user}:{pw}".encode()).decode()
    url = f"https://{domain}/wp-json/wp/v2/{ep}"
    if params: url += '?' + urllib.parse.urlencode(params)
    body = json.dumps(data).encode() if data is not None else None
    req = urllib.request.Request(url, data=body, method=method, headers={
        'Authorization': f'Basic {auth}', 'Content-Type':'application/json', 'User-Agent':'Bot/1.0'})
    with urllib.request.urlopen(req, timeout=60) as r:
        return json.loads(r.read())

def has_post_on_date(domain, user, pw, target_date):
    """Cek apakah website sudah punya post publish/future/draft utk tanggal sasaran (UTC)."""
    # jadwal 09:00 WIB = 02:00 UTC, query rentang hari itu
    start = target_date + 'T00:00:00'
    end   = (datetime.date.fromisoformat(target_date) + datetime.timedelta(days=1)).isoformat() + 'T00:00:00'
    page = 1
    while True:
        try:
            posts = wp_req(domain, user, pw, 'posts', params={
                'per_page':100,'page':page,'status':'publish,future,draft',
                'after':start,'before':end,'_fields':'id,date,slug,title'})
        except urllib.error.HTTPError as e:
            if e.code == 400: break
            raise
        except Exception:
            break
        if not posts: break
        for p in posts:
            d = (p.get('date') or '')[:10]
            if d == target_date:
                return True
        if len(posts) < 100: break
        page += 1
    return False

def all_post_hashes(domain, user, pw, limit=50):
    """Hash isi semua post terbaru utk deteksi konten duplikat."""
    hashes = {}
    try:
        posts = wp_req(domain, user, pw, 'posts', params={'per_page':limit,'status':'publish','_fields':'slug,content'})
        for p in posts:
            slug = p.get('slug','')
            c = p.get('content',{})
            text = c.get('rendered','') if isinstance(c,dict) else str(c)
            hashes[slug] = hashlib.md5(re.sub(r'<[^>]+>','',text).encode()).hexdigest()
    except Exception:
        pass
    return hashes

# ------------------------------------------------------------ pexels (cari gambar)
def find_pexels(keyword, width=1260, height=750):
    """Cari gambar Pexels. Key dibaca dari env/file config. Return URL atau None."""
    key = os.environ.get('PEXELS_KEY','')
    if not key:
        # cari key di file config umum
        for f in ['pexels_key.txt','.pexels_key', os.path.join(WS,'pexels_key.txt')]:
            if os.path.exists(f):
                key = open(f).read().strip()
                if key: break
    if not key:
        return None
    q = urllib.parse.quote(keyword)
    url = f"https://api.pexels.com/v1/search?query={q}&per_page=1&orientation=landscape"
    req = urllib.request.Request(url, headers={'Authorization': key})
    try:
        with urllib.request.urlopen(req, timeout=30) as r:
            data = json.loads(r.read())
            if data.get('photos'):
                ph = data['photos'][0]
                return f"{ph['src']['original']}?auto=compress&cs=tinysrgb&w={width}&h={height}&dpr=1"
    except Exception:
        pass
    return None

# ------------------------------------------------------------ tracker
def load_tracker():
    if os.path.exists(TRACKER):
        try: return json.load(open(TRACKER))
        except: pass
    return {"used": {}}

def save_tracker(tr):
    json.dump(tr, open(TRACKER,'w'), ensure_ascii=False, indent=1)

def used_slugs(client, tr):
    return set(tr['used'].get(client, {}).keys())

def mark_used(client, slug, date, hashv, post_id, url):
    tr = load_tracker()
    tr['used'].setdefault(client, {})[slug] = {
        'date': date, 'hash': hashv, 'post_id': post_id, 'url': url}
    save_tracker(tr)

# ------------------------------------------------------------ log
def log_line(entry):
    with open(LOG,'a') as f:
        f.write(json.dumps(entry, ensure_ascii=False) + '\n')

# ------------------------------------------------------------ upload media
import urllib.request as _ur

def upload_media(domain, user, pw, image_url, filename):
    """Download gambar dari Pexels, upload ke WP, return media id."""
    # --- download ---
    req = _ur.Request(image_url, headers={'User-Agent':'Mozilla/5.0'})
    img_bytes = _ur.urlopen(req, timeout=60).read()
    # --- upload via media endpoint (base64 JSON) ---
    auth = base64.b64encode(f"{user}:{pw}".encode()).decode()
    data = {
        'file': base64.b64encode(img_bytes).decode(),
        'filename': f"{filename}.jpg",
        'caption': filename,
        'alt_text': filename,
    }
    url = f"https://{domain}/wp-json/wp/v2/media"
    body = json.dumps(data).encode()
    req = _ur.Request(url, data=body, method='POST', headers={
        'Authorization': f'Basic {auth}', 'Content-Type':'application/json',
        'User-Agent':'Bot/1.0', 'Content-Disposition': f'attachment; filename={filename}.jpg'})
    with _ur.urlopen(req, timeout=90) as r:
        return r.headers.get('X-WP-Total') and json.loads(r.read()).get('id')

def main():
    args = [a for a in sys.argv[1:]]
    only_client = None
    target = None
    if '--client' in args:
        only_client = args[args.index('--client')+1]
    if '--date' in args:
        target = args[args.index('--date')+1]
    if not target:
        # besok 09:00 WIB
        target = (datetime.date.today() + datetime.timedelta(days=1)).isoformat()

    pool = json.load(open(POOL))
    tr = load_tracker()

    clients = list(pool.keys())
    if only_client: clients = [c for c in clients if only_client.lower() in c.lower()]

    results = []
    for client in clients:
        creds = get_creds_for_client(client)
        if not creds:
            results.append({'client':client,'status':'SKIP','reason':'cfg_*.py tidak ditemukan'})
            continue
        dom, user, pw = creds['domain'], creds['user'], creds['pass']

        # Lapis 2: sudah ada post utk tanggal sasaran?
        try:
            if has_post_on_date(dom, user, pw, target):
                results.append({'client':client,'status':'SKIP','reason':f'sudah ada post {target}'})
                continue
        except Exception as e:
            results.append({'client':client,'status':'ERR','reason':f'cek tgl gagal: {e}'})
            continue

        # pilih artikel belum-terpakai
        used = used_slugs(client, tr)
        cands = [a for a in pool[client] if a['slug'] not in used]
        if not cands:
            results.append({'client':client,'status':'SKIP','reason':'semua stok sudah terpakai'})
            continue

        art = cands[0]

        # normalisasi content → list of [type, value] (tangani dict {type,t} & list [type,val])
        norm_content = []
        for b in art['content']:
            if isinstance(b, dict):
                typ = b.get('type','p'); val = b.get('t', b.get('items',''))
            else:
                typ, val = b[0], b[1]
            norm_content.append([typ, val])
        art['content'] = norm_content

        # Lapis 3: hash isi unik vs post existing
        body_text = ' '.join(b[1] if isinstance(b[1],str) else ' '.join(b[1]) for b in art['content'])
        ah = hashlib.md5(re.sub(r'<[^>]+>','',body_text).encode()).hexdigest()
        try:
            existing_hashes = all_post_hashes(dom, user, pw)
            if ah in existing_hashes.values():
                results.append({'client':client,'status':'SKIP','reason':'konten identik sudah ada di website','slug':art['slug']})
                continue
        except Exception:
            pass

        # featured image
        kw = art['focus_kw'] or art['judul']
        img = find_pexels(kw)
        if not img:
            # fallback gambar generik dari cfg asli kalau ada
            img = creds['article'].get('image_url') if isinstance(creds.get('article'),dict) else None

        # build payload (format pipeline)
        cats = art.get('categories') or ['Artikel']
        tags = art.get('tags') or []
        content_html = []
        for b in art['content']:
            typ, val = b[0], b[1]
            if typ == 'h2':
                content_html.append(f"<h2>{val}</h2>")
            elif typ == 'ul':
                items = "".join(f"<li>{x}</li>" for x in val)
                content_html.append(f"<ul>{items}</ul>")
            else:
                content_html.append(f"<p>{val}</p>")
        html = ''.join(content_html)

        payload = {
            "title": art['judul'],
            "slug": art['slug'],
            "content": html,
            "excerpt": art['meta_desc'][:150],
            "status": "future",
            "date": f"{target}T02:00:00",
            "categories": [c for c in cats if isinstance(c,int)] or None,
            "meta": {"_yoast_wpseo_metadesc": art['meta_desc'][:155],
                     "_yoast_wpseo_title": art['judul'][:60]},
        }
        # upload featured image kalau dapat
        media_id = None
        if img:
            try:
                media_id = upload_media(dom, user, pw, img, art['slug'])
                if media_id: payload['featured_media'] = media_id
            except Exception:
                media_id = None

        try:
            post = wp_req(dom, user, pw, 'posts', method='POST', data=payload)
            pid = post.get('id'); link = post.get('link')
            mark_used(client, art['slug'], target, ah, pid, link)
            log_line({'ts':datetime.datetime.utcnow().isoformat(),'client':client,
                      'slug':art['slug'],'post_id':pid,'url':link,'date':target,'status':'OK'})
            results.append({'client':client,'status':'OK','slug':art['slug'],'url':link,'id':pid})
        except urllib.error.HTTPError as e:
            err = e.read().decode(errors='ignore')[:300] if hasattr(e,'read') else str(e)
            results.append({'client':client,'status':'ERR','reason':err,'slug':art['slug']})

    # print ringkasan
    print('='*60)
    print(f'HASIL PUBLISH HARIAN — target {target}')
    print('='*60)
    for r in results:
        s = r['status']
        icon = {'OK':'✅','SKIP':'⏭️','ERR':'❌'}.get(s,s)
        extra = f" → {r.get('url')}" if s=='OK' else f" — {r.get('reason')}"
        print(f'{icon} {r["client"]}: {s}{extra}')

if __name__ == '__main__':
    main()
