#!/usr/bin/env python3
"""StudioSoft: bangun dasbor HTML dari GSC + GA4 (enam KPI utama + pendukungnya).
Dijalankan mingguan oleh cron; hasilnya satu berkas HTML mandiri + ringkasan teks."""
import html, json, sys, urllib.parse, urllib.request, urllib.error
from datetime import date, datetime, timedelta

WS = "/home/mantapman/.hermes/profiles/atun/workspace"
TOKEN = f"{WS}/seo-oauth/google_token_atun.json"
GA4 = "properties/478760195"
KELUAR = f"{WS}/drafts/dashboard_studiosoft.html"
CACHE = f"{WS}/drafts/dashboard_studiosoft.json"

KATA_UANG = ["jasa seo surabaya", "digital agency surabaya", "digital marketing agency surabaya",
             "branding agency surabaya", "seo surabaya"]

try:
    from google.oauth2.credentials import Credentials
    from google.auth.transport.requests import Request
    c = Credentials.from_authorized_user_file(TOKEN)
    if c.expired and c.refresh_token:
        c.refresh(Request()); open(TOKEN, "w").write(c.to_json())
except Exception as e:
    print("❌ Token Google tidak bisa dipakai:", str(e)[:160]); sys.exit(0)

H = {"Authorization": f"Bearer {c.token}", "Content-Type": "application/json"}


def api(u, data=None):
    r = urllib.request.Request(u, headers=H, data=json.dumps(data).encode() if data else None)
    for _ in range(3):
        try:
            with urllib.request.urlopen(r, timeout=180) as x:
                return json.loads(x.read())
        except urllib.error.HTTPError as e:
            return {"err": e.code}
        except Exception:
            import time; time.sleep(3)
    return {}


SITE = "https://studiosoft.id/"
SITE_ENC = urllib.parse.quote(SITE, safe="")


def gsc(dim, a, b, baris=500):
    d = api(f"https://www.googleapis.com/webmasters/v3/sites/{SITE_ENC}/searchAnalytics/query",
            {"startDate": a, "endDate": b, "dimensions": dim, "rowLimit": baris})
    return (d or {}).get("rows", [])


def ga(dim, met, hari=90, limit=20, urut="sessions"):
    d = api(f"https://analyticsdata.googleapis.com/v1beta/{GA4}:runReport",
            {"dateRanges": [{"startDate": f"{hari}daysAgo", "endDate": "today"}],
             "dimensions": [{"name": x} for x in dim],
             "metrics": [{"name": x} for x in met], "limit": limit,
             "orderBys": [{"metric": {"metricName": urut}, "desc": True}]})
    return (d or {}).get("rows", [])


akhir = date.today() - timedelta(days=3)
awal = akhir - timedelta(days=27)
lalu_akhir = awal - timedelta(days=1)
lalu_awal = lalu_akhir - timedelta(days=27)
S = lambda x: x.isoformat()


def total_gsc(a, b):
    rows = gsc(["date"], a, b, 100)
    klik = sum(r["clicks"] for r in rows)
    tayang = sum(r["impressions"] for r in rows)
    pos = [r["position"] for r in rows]
    return {"klik": klik, "tayang": tayang, "ctr": round(klik / tayang * 100, 2) if tayang else 0,
            "posisi": round(sum(pos) / len(pos), 1) if pos else 0}


ini = total_gsc(S(awal), S(akhir))
lalu = total_gsc(S(lalu_awal), S(lalu_akhir))

rows90 = gsc(["query"], S(akhir - timedelta(days=89)), S(akhir), 3000)
pita = []
for lo, hi in ((1, 3), (4, 10), (11, 20), (21, 50), (51, 100)):
    kl = sum(r["clicks"] for r in rows90 if lo <= r["position"] <= hi)
    ty = sum(r["impressions"] for r in rows90 if lo <= r["position"] <= hi)
    pita.append({"pita": f"{lo}-{hi}", "klik": kl, "tayang": ty, "ctr": round(kl / ty * 100, 2) if ty else 0})

posisi_uang = []
for k in KATA_UANG:
    x = next((r for r in rows90 if r["keys"][0].lower() == k), None)
    posisi_uang.append({"kata": k, "posisi": round(x["position"], 1) if x else None,
                        "tayang": x["impressions"] if x else 0, "klik": x["clicks"] if x else 0})

kueri_atas = [{"k": r["keys"][0], "klik": r["clicks"], "tayang": r["impressions"],
               "pos": round(r["position"], 1)} for r in sorted(rows90, key=lambda z: -z["impressions"])[:12]]
halaman = [{"u": r["keys"][0].replace("https://studiosoft.id", "") or "/", "klik": r["clicks"],
            "tayang": r["impressions"], "pos": round(r["position"], 1)}
           for r in sorted(gsc(["page"], S(awal), S(akhir), 200), key=lambda z: -z["clicks"])[:10]]
kanal = [{"k": r["dimensionValues"][0]["value"], "sesi": int(r["metricValues"][0]["value"]),
          "keterlibatan": round(float(r["metricValues"][1]["value"]) * 100),
          "kunci": r["metricValues"][2]["value"]} for r in ga(["sessionDefaultChannelGroup"],
                                                             ["sessions", "engagementRate", "keyEvents"])]
peristiwa = [{"e": r["dimensionValues"][0]["value"], "n": r["metricValues"][0]["value"]}
             for r in ga(["eventName"], ["eventCount"], 90, 25, "eventCount")]
sesi_total = sum(k["sesi"] for k in kanal)
sesi_organik = next((k["sesi"] for k in kanal if k["k"] == "Organic Search"), 0)
sesi_ai = next((k["sesi"] for k in kanal if k["k"] == "AI Assistant"), 0)


def delta(now, before):
    if not before:
        return "—", 0
    p = (now - before) / before * 100
    return f"{p:+.0f}%", p


def warna(p):
    return "#0f9d58" if p > 1 else ("#d93025" if p < -1 else "#5f6368")


kartu = [
    ("Klik organik (28 hari)", ini["klik"], lalu["klik"], "GSC", "naik"),
    ("Tayangan (28 hari)", ini["tayang"], lalu["tayang"], "GSC", "naik"),
    ("CTR rata-rata", ini["ctr"], lalu["ctr"], "GSC", "naik"),
    # posisi: angka LEBIH KECIL = lebih baik, jadi arahnya dibalik
    ("Posisi rata-rata", ini["posisi"], lalu["posisi"], "GSC", "turun"),
    ("Sesi organic search (90 hari)", sesi_organik, 0, "GA4", "naik"),
    ("Sesi kanal AI Assistant (90 hari)", sesi_ai, 0, "GA4", "naik"),
]

kunci_wa = next((p["n"] for p in peristiwa if p["e"] in ("klik_wa", "wa_click", "click_whatsapp")), "0")

baris_kartu = ""
for nama, nilai, sebelum, alat, arah in kartu:
    if sebelum:
        teks_delta, p = delta(float(nilai), float(sebelum))
        # arah "turun" (posisi): nilai turun = baik
        baik = (p < -1) if arah == "turun" else (p > 1)
        buruk = (p > 1) if arah == "turun" else (p < -1)
        warna_delta = "#0f9d58" if baik else ("#d93025" if buruk else "#5f6368")
        ket = "membaik" if baik else ("memburuk" if buruk else "datar")
        catatan = f'<span style="color:{warna_delta}">{teks_delta} {ket} vs 28 hari sebelumnya</span>'
    else:
        catatan = '<span style="color:#5f6368">tanpa pembanding</span>'
    baris_kartu += (f'<div class="kartu"><div class="label">{nama}</div>'
                    f'<div class="nilai">{nilai}</div><div class="catatan">{catatan} · {alat}</div></div>')

baris_pita = ""
maks = max((p["tayang"] for p in pita), default=1)
for p in pita:
    lebar = round(p["tayang"] / maks * 100) if maks else 0
    baris_pita += (f'<tr><td>posisi {p["pita"]}</td>'
                   f'<td><div class="bar"><span style="width:{lebar}%"></span></div></td>'
                   f'<td class="angka">{p["tayang"]:,}</td><td class="angka">{p["klik"]}</td>'
                   f'<td class="angka">{p["ctr"]}%</td></tr>')

baris_uang = ""
for p in posisi_uang:
    tanda = "—" if p["posisi"] is None else p["posisi"]
    warna_pos = "#0f9d58" if (p["posisi"] or 99) <= 10 else ("#f9ab00" if (p["posisi"] or 99) <= 30 else "#d93025")
    baris_uang += (f'<tr><td>{p["kata"]}</td><td class="angka" style="color:{warna_pos};font-weight:600">{tanda}</td>'
                   f'<td class="angka">{p["tayang"]:,}</td><td class="angka">{p["klik"]}</td></tr>')

baris_kanal = ""
for k in kanal:
    baris_kanal += (f'<tr><td>{k["k"]}</td><td class="angka">{k["sesi"]:,}</td>'
                    f'<td class="angka">{k["keterlibatan"]}%</td><td class="angka">{k["kunci"]}</td></tr>')

baris_kueri = ""
for k in kueri_atas:
    baris_kueri += (f'<tr><td>{html.escape(k["k"][:58])}</td><td class="angka">{k["tayang"]:,}</td>'
                    f'<td class="angka">{k["klik"]}</td><td class="angka">{k["pos"]}</td></tr>')

baris_halaman = ""
for h in halaman:
    baris_halaman += (f'<tr><td>{html.escape(h["u"][:62])}</td><td class="angka">{h["klik"]}</td>'
                      f'<td class="angka">{h["tayang"]:,}</td><td class="angka">{h["pos"]}</td></tr>')

waktu = datetime.now().strftime("%d %B %Y, %H:%M WIB")
htm = f"""<!DOCTYPE html><html lang="id"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dasbor KPI StudioSoft — {waktu}</title>
<style>
 body{{font-family:-apple-system,Segoe UI,Roboto,sans-serif;background:#f6f7f9;color:#202124;margin:0;padding:24px}}
 h1{{font-size:22px;margin:0 0 4px}} .sub{{color:#5f6368;font-size:13px;margin-bottom:20px}}
 .grid{{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:12px;margin-bottom:22px}}
 .kartu{{background:#fff;border:1px solid #e3e6ea;border-radius:10px;padding:14px 16px}}
 .label{{font-size:12px;color:#5f6368}} .nilai{{font-size:28px;font-weight:700;margin:6px 0 2px}}
 .catatan{{font-size:12px}}
 h2{{font-size:15px;margin:24px 0 8px;color:#202124}}
 table{{width:100%;border-collapse:collapse;background:#fff;border:1px solid #e3e6ea;border-radius:10px;overflow:hidden}}
 th,td{{padding:9px 12px;font-size:13px;text-align:left;border-bottom:1px solid #eef0f2}}
 th{{background:#fafbfc;color:#5f6368;font-weight:600}} .angka{{text-align:right}}
 .bar{{background:#eef0f2;border-radius:4px;height:10px;width:100%}} .bar span{{display:block;height:10px;background:#1a73e8;border-radius:4px}}
 footer{{color:#5f6368;font-size:12px;margin-top:26px}}
</style></head><body>
<h1>Dasbor KPI StudioSoft</h1>
<div class="sub">Sumber: Google Search Console + GA4 (properti 478760195) · dibuat otomatis {waktu} · periode GSC sampai {S(akhir)}</div>
<div class="grid">{baris_kartu}</div>

<h2>Sebaran posisi (90 hari) — hampir semua tayangan belum menghasilkan klik</h2>
<table><thead><tr><th>Pita posisi</th><th>Tayangan</th><th class="angka">Tayangan</th><th class="angka">Klik</th><th class="angka">CTR</th></tr></thead>
<tbody>{baris_pita}</tbody></table>

<h2>Posisi kata kunci uang</h2>
<table><thead><tr><th>Kata kunci</th><th class="angka">Posisi</th><th class="angka">Tayangan</th><th class="angka">Klik</th></tr></thead>
<tbody>{baris_uang}</tbody></table>

<h2>Kanal kedatangan (GA4, 90 hari) — total {sesi_total:,} sesi</h2>
<table><thead><tr><th>Kanal</th><th class="angka">Sesi</th><th class="angka">Keterlibatan</th><th class="angka">Peristiwa kunci</th></tr></thead>
<tbody>{baris_kanal}</tbody></table>

<h2>Kueri tayangan terbanyak (90 hari)</h2>
<table><thead><tr><th>Kueri</th><th class="angka">Tayangan</th><th class="angka">Klik</th><th class="angka">Posisi</th></tr></thead>
<tbody>{baris_kueri}</tbody></table>

<h2>Halaman teratas (28 hari)</h2>
<table><thead><tr><th>Halaman</th><th class="angka">Klik</th><th class="angka">Tayangan</th><th class="angka">Posisi</th></tr></thead>
<tbody>{baris_halaman}</tbody></table>

<footer>Peristiwa klik WhatsApp pada 90 hari: <strong>{kunci_wa}</strong> — selama angkanya nol, laporan berhenti di "berapa yang datang" dan belum sampai "berapa yang menghubungi".
Data GSC selalu tiga hari di belakang. Dibuat oleh Atun · cron mingguan.</footer>
</body></html>"""

open(KELUAR, "w").write(htm)
json.dump({"ini": ini, "lalu": lalu, "pita": pita, "posisi_uang": posisi_uang, "kanal": kanal,
           "kueri_atas": kueri_atas, "halaman": halaman, "peristiwa": peristiwa, "sesi_total": sesi_total,
           "sesi_organik": sesi_organik, "sesi_ai": sesi_ai, "klik_wa": kunci_wa},
          open(CACHE, "w"), indent=1, ensure_ascii=False)

d_klik, _ = delta(ini["klik"], lalu["klik"])
d_tayang, _ = delta(ini["tayang"], lalu["tayang"])
print("Dasbor KPI StudioSoft — dibuat", waktu)
print(f"  Klik organik 28 hari : {ini['klik']} ({d_klik} vs sebelumnya)")
print(f"  Tayangan 28 hari     : {ini['tayang']:,} ({d_tayang})")
print(f"  CTR rata-rata        : {ini['ctr']}% (sebelumnya {lalu['ctr']}%)")
print(f"  Posisi rata-rata     : {ini['posisi']} (sebelumnya {lalu['posisi']})")
print(f"  Sesi 90 hari         : {sesi_total:,} | organik {sesi_organik} | AI Assistant {sesi_ai}")
print(f"  Klik WhatsApp (90h)  : {kunci_wa}")
print(f"  Berkas               : {KELUAR}")
