#!/usr/bin/env python3
"""koe-node — 声の分散ネットワークのワーカー。

招待トークンで orchestrator に登録し、ジョブをポーリングして自分の Mac で
合成して返す。参照音声はジョブに同梱され、ディスクに書かず**メモリ上だけ**で
扱う（無保存）。依存は Python 標準ライブラリのみ。

env:
  KOE_ORCH       司令塔URL          (default https://voice.koe.live)
  KOE_INVITE     招待トークン        (必須)
  KOE_WORKER     自機workerURL      (default http://127.0.0.1:8790)
  TTS_TOKEN      自機worker Bearer   (必須)
  KOE_NODE_NAME  表示名             (default ホスト名)
"""
import os
import sys
import time
import json
import base64
import hashlib
import urllib.request

ORCH = os.environ.get("KOE_ORCH", "https://voice.koe.live").rstrip("/")
INVITE = os.environ.get("KOE_INVITE", "")
WORKER = os.environ.get("KOE_WORKER", "http://127.0.0.1:8790").rstrip("/")
WTOKEN = os.environ.get("TTS_TOKEN", "")
NAME = os.environ.get("KOE_NODE_NAME", os.uname().nodename)
ACCT = os.environ.get("KOE_ACCT", "")


def _agent_hash():
    """この agent ファイル自身の SHA256（無保存・改変検知の自己申告用）。"""
    try:
        return hashlib.sha256(open(__file__, "rb").read()).hexdigest()[:16]
    except Exception:
        return "unknown"


def _post(url, obj, headers=None, timeout=120):
    data = json.dumps(obj).encode()
    h = {"Content-Type": "application/json"}
    h.update(headers or {})
    rq = urllib.request.Request(url, data=data, headers=h)
    with urllib.request.urlopen(rq, timeout=timeout) as r:
        return r.read()


def register():
    out = json.loads(_post(ORCH + "/node/register",
                           {"invite": INVITE, "name": NAME, "agent_hash": _agent_hash(), "acct": ACCT}))
    return out["node_token"], out["node_id"]


def synth_local(job):
    """自機の worker(/worker/synth) で合成。ref はメモリ上で渡すのみ・無保存。"""
    body = {"text": job["text"], "lang": job.get("lang", "ja"), "fast": job.get("fast", False)}
    if job.get("ref_audio_b64"):
        body["ref_audio_b64"] = job["ref_audio_b64"]
    raw = _post(WORKER + "/worker/synth", body,
                {"Authorization": "Bearer " + WTOKEN}, timeout=240)
    return base64.b64encode(raw).decode()


def main():
    if not INVITE:
        sys.exit("KOE_INVITE required")
    tok, nid = register()
    print("registered node %s as %s -> %s (agent %s)" % (nid, NAME, ORCH, _agent_hash()), flush=True)
    while True:
        try:
            res = json.loads(_post(ORCH + "/node/poll", {"node_token": tok}, timeout=35))
            job = res.get("job")
            if not job:
                time.sleep(1.0)
                continue
            print("job %s (%d chars)" % (job["job_id"], len(job.get("text", ""))), flush=True)
            mp3 = synth_local(job)
            _post(ORCH + "/node/result",
                  {"node_token": tok, "job_id": job["job_id"], "mp3_b64": mp3})
            print("done %s" % job["job_id"], flush=True)
        except Exception as e:
            print("err: %s" % e, flush=True)
            time.sleep(2.0)


if __name__ == "__main__":
    main()
