#!/usr/bin/env python3
"""
accounts.py — registry akun job portal di mesin Hermes (1 mesin = 1 user).

Menyimpan status koneksi akun per portal secara lokal (accounts.json) dan
MENYINKRONKANNYA ke BE platform via POST /v1/agent/portal-status (jika config
BE terisi). Agent jobseeker membaca registry ini SEBELUM melamar, dan
memperbaruinya SETELAH verifikasi login (via browser).

Status:
  connected      — sesi login aktif & terverifikasi
  connecting     — sedang proses login (agent buka halaman, user mengetik)
  expired        — sesi pernah aktif tapi kedaluwarsa (perlu login ulang)
  disconnected   — belum pernah terhubung / sengaja diputus

Config BE (opsional, dari agent-config.env di folder yang sama):
  BE_API_URL, BE_INTERNAL_TOKEN, USER_ID
  → jika ada, setiap perubahan status juga di-POST ke {BE_API_URL}/v1/agent/portal-status
  → `sync` mengirim seluruh registry (untuk sinkronisasi awal / recovery)

Pemakaian:
  python3 accounts.py list
  python3 accounts.py status jobstreet
  python3 accounts.py set jobstreet --status connected --display-name "ena***@yahoo.com"
  python3 accounts.py set jobstreet --status expired --note "session 401"
  python3 accounts.py sync
  python3 accounts.py rm jobstreet
"""
import argparse
import json
import os
import sys
import urllib.request
import urllib.error
from datetime import datetime, timezone
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent
DATA_FILE = BASE_DIR / "accounts.json"
CONFIG_FILE = BASE_DIR / "agent-config.env"

# Portal yang didukung (nama key -> nama tampilan). Fallback — daftar utama
# kini dari BE (portals.json hasil GET /v1/agent/portals, diisi portal-check.py).
PORTALS = {
    "jobstreet": "JobStreet",
    "linkedin": "LinkedIn",
    "indeed": "Indeed",
    "glints": "Glints",
    "kalibrr": "Kalibrr",
    "techinasia": "Tech in Asia",
}

VALID_STATUS = {"connected", "disconnected", "connecting", "expired"}


def portal_registry() -> dict:
    """Nama tampilan per portal: gabungan fallback bawaan + portals.json (dari BE)."""
    names = dict(PORTALS)
    try:
        pf = BASE_DIR / "portals.json"
        if pf.exists():
            data = json.loads(pf.read_text())
            for slug, info in data.items():
                if isinstance(info, dict) and info.get("name"):
                    names[slug] = info["name"]
    except Exception:
        pass
    return names


def is_known_portal(slug: str) -> bool:
    if slug in PORTALS:
        return True
    try:
        pf = BASE_DIR / "portals.json"
        return pf.exists() and slug in json.loads(pf.read_text())
    except Exception:
        return False


def now_iso() -> str:
    return datetime.now(timezone.utc).isoformat(timespec="seconds")


# ---------------- config & data ----------------

def load_config() -> dict:
    cfg = {
        "BE_API_URL": os.environ.get("BE_API_URL", ""),
        "BE_INTERNAL_TOKEN": os.environ.get("BE_INTERNAL_TOKEN", ""),
        "USER_ID": os.environ.get("USER_ID", ""),
    }
    if CONFIG_FILE.exists():
        for line in CONFIG_FILE.read_text().splitlines():
            line = line.strip()
            if not line or line.startswith("#") or "=" not in line:
                continue
            k, _, v = line.partition("=")
            k, v = k.strip(), v.strip()
            if k in cfg and not cfg[k]:
                cfg[k] = v
    return cfg


def load() -> dict:
    if DATA_FILE.exists():
        try:
            return json.loads(DATA_FILE.read_text())
        except Exception as e:
            print(f"ERROR: accounts.json corrupt: {e}", file=sys.stderr)
            sys.exit(2)
    return {}


def save(data: dict):
    DATA_FILE.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n")


# ---------------- sync ke BE ----------------

def sync_to_be(cfg: dict, portal: str, entry: dict) -> tuple:
    """POST satu entri ke BE. Return (ok: bool, detail: str)."""
    if not (cfg.get("BE_API_URL") and cfg.get("BE_INTERNAL_TOKEN") and cfg.get("USER_ID")):
        return (False, "config BE belum diisi (BE_API_URL/BE_INTERNAL_TOKEN/USER_ID)")
    url = cfg["BE_API_URL"].rstrip("/") + "/v1/agent/portal-status"
    payload = {
        "user_id": cfg["USER_ID"],
        "portal": portal,
        "status": entry.get("status", "disconnected"),
    }
    for k in ("display_name", "connected_at", "verified_at", "note"):
        if entry.get(k):
            payload[k] = entry[k]
    body = json.dumps(payload).encode()
    req = urllib.request.Request(
        url,
        data=body,
        method="POST",
        headers={
            "Content-Type": "application/json",
            "X-Internal-Token": cfg["BE_INTERNAL_TOKEN"],
        },
    )
    try:
        with urllib.request.urlopen(req, timeout=20) as resp:
            return (True, f"HTTP {resp.status}")
    except urllib.error.HTTPError as e:
        detail = e.read().decode()[:120]
        return (False, f"HTTP {e.code}: {detail}")
    except Exception as e:
        return (False, str(e))


# ---------------- commands ----------------

def cmd_list():
    data = load()
    if not data:
        print("(belum ada akun terhubung)")
        return
    for portal, info in data.items():
        name = portal_registry().get(portal, portal)
        status = info.get("status", "?")
        disp = info.get("display_name") or "-"
        ver = (info.get("verified_at") or "-")[:16]
        note = f"  ({info['note']})" if info.get("note") else ""
        print(f"{name:<14} {status:<13} {disp:<24} verified:{ver}{note}")


def cmd_status(portal):
    data = load()
    info = data.get(portal)
    if not info:
        print(f"{portal}: disconnected (belum pernah terhubung)")
        sys.exit(0)
    print(json.dumps(info, indent=2, ensure_ascii=False))


def cmd_set(args):
    if not is_known_portal(args.portal):
        print(f"ERROR: portal '{args.portal}' tidak dikenal. Pilih: {', '.join(portal_registry())}", file=sys.stderr)
        sys.exit(2)
    if args.status not in VALID_STATUS:
        print(f"ERROR: status harus salah satu dari: {', '.join(sorted(VALID_STATUS))}", file=sys.stderr)
        sys.exit(2)
    data = load()
    entry = data.get(args.portal, {})
    entry["status"] = args.status
    if args.display_name:
        entry["display_name"] = args.display_name
    if args.note:
        entry["note"] = args.note
    if args.status == "connected":
        entry["connected_at"] = entry.get("connected_at") or now_iso()
        entry["verified_at"] = now_iso()
        entry.pop("note", None)
    elif args.status == "expired":
        entry["note"] = args.note or entry.get("note") or "sesi kedaluwarsa"
    data[args.portal] = entry
    save(data)
    print(f"OK: {args.portal} -> {args.status}")

    # Sinkronisasi best-effort ke BE
    cfg = load_config()
    ok, detail = sync_to_be(cfg, args.portal, entry)
    if ok:
        print(f"   synced ke BE ({detail})")
    else:
        print(f"   warning: belum sync ke BE — {detail}")


def cmd_sync():
    cfg = load_config()
    if not (cfg.get("BE_API_URL") and cfg.get("BE_INTERNAL_TOKEN") and cfg.get("USER_ID")):
        print("ERROR: config BE belum diisi (BE_API_URL/BE_INTERNAL_TOKEN/USER_ID)", file=sys.stderr)
        sys.exit(2)
    data = load()
    if not data:
        print("(registry kosong — tidak ada yang di-sync)")
        return
    for portal, entry in data.items():
        ok, detail = sync_to_be(cfg, portal, entry)
        print(f"{'OK ' if ok else 'GAGAL'} {portal}: {detail}")
    sys.exit(0 if all(sync_to_be(cfg, p, e)[0] for p, e in data.items()) else 1)


def cmd_rm(portal):
    data = load()
    if portal in data:
        del data[portal]
        save(data)
        print(f"OK: {portal} dihapus (jalankan `sync` untuk mencerminkan ke BE)")
    else:
        print(f"INFO: {portal} tidak ada di registry")


def main():
    p = argparse.ArgumentParser(description="Registry akun job portal (+ sync ke BE)")
    sub = p.add_subparsers(dest="cmd", required=True)

    sub.add_parser("list")
    ps = sub.add_parser("status")
    ps.add_argument("portal")
    st = sub.add_parser("set")
    st.add_argument("portal")
    st.add_argument("--status", required=True)
    st.add_argument("--display-name")
    st.add_argument("--note")
    sub.add_parser("sync")
    rm = sub.add_parser("rm")
    rm.add_argument("portal")

    args = p.parse_args()
    if args.cmd == "list":
        cmd_list()
    elif args.cmd == "status":
        cmd_status(args.portal)
    elif args.cmd == "set":
        cmd_set(args)
    elif args.cmd == "sync":
        cmd_sync()
    elif args.cmd == "rm":
        cmd_rm(args.portal)


if __name__ == "__main__":
    main()
