"""
Pre-Uni New College Lesson Voice Creator - command-line client (for Codex or anyone).

  python lvc_client.py --code QID191568 --text-file script.txt --pauses "1.77 / 1.62 / 1.70" --out audio
  python lvc_client.py --batch scripts.csv --out audio        (CSV columns: code,text[,pauses]  or  code,text_file[,pauses])

Server: --server or LVC_SERVER   Token: --token or LVC_TOKEN (make one on the site's Codex page)
Exit codes: 0 ok, 1 some failed, 2 cannot reach server / bad token, 3 monthly budget reached.
Only the Python standard library is used.
"""
import argparse, csv, json, os, sys, urllib.error, urllib.request
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path

DEFAULT_SERVER = "https://preuni-lesson-voice-creator.pages.dev"


def post(server, token, payload):
    req = urllib.request.Request(server.rstrip("/") + "/api/tts", data=json.dumps(payload).encode(),
                                 headers={"Content-Type": "application/json", "Authorization": "Bearer " + token})
    try:
        with urllib.request.urlopen(req, timeout=600) as r:
            return r.status, json.loads(r.read().decode())
    except urllib.error.HTTPError as e:
        try:
            return e.code, json.loads(e.read().decode())
        except ValueError:
            return e.code, {"message": "HTTP %d" % e.code}
    except Exception as e:  # noqa: BLE001
        return 0, {"message": "Cannot reach %s: %s" % (server, e)}


def one(a, code, text, pauses):
    payload = {"code": code, "text": text, "source": "api", "force": a.force}
    if pauses:
        payload["pauses"] = pauses
    st, res = post(a.server, a.token, payload)
    out = {"code": code, "http_status": st, "ok": bool(res.get("ok"))}
    if res.get("ok"):
        dest = Path(a.out) / res["file_name"]
        dest.parent.mkdir(parents=True, exist_ok=True)
        with urllib.request.urlopen(res["download_url"], timeout=300) as r:
            dest.write_bytes(r.read())
        out.update(file=str(dest.resolve()), duration_sec=res["duration_sec"], cached=res["cached"],
                   cost_usd=res["cost_usd"], paragraphs=res["paragraphs"])
    else:
        out["error"] = res.get("message") or res.get("error")
    return out


def main():
    ap = argparse.ArgumentParser(description="Make lesson narration MP3s")
    ap.add_argument("--server", default=os.environ.get("LVC_SERVER", DEFAULT_SERVER))
    ap.add_argument("--token", default=os.environ.get("LVC_TOKEN", ""))
    ap.add_argument("--code"); ap.add_argument("--text"); ap.add_argument("--text-file")
    ap.add_argument("--pauses", help='e.g. "1.77 / 1.62 / 1.70"')
    ap.add_argument("--batch"); ap.add_argument("--workers", type=int, default=3)
    ap.add_argument("--out", default="audio", help="folder for the MP3 files")
    ap.add_argument("--force", action="store_true", help="regenerate even if cached (costs money)")
    a = ap.parse_args()
    if not a.token:
        sys.exit("Set LVC_TOKEN (make a token on the site's Codex page).")
    jobs = []
    if a.batch:
        base = Path(a.batch).resolve().parent
        with open(a.batch, newline="", encoding="utf-8-sig") as f:
            for row in csv.DictReader(f):
                text = row.get("text") or ""
                if not text and row.get("text_file"):
                    text = (base / row["text_file"]).read_text(encoding="utf-8")
                jobs.append((row["code"].strip(), text, row.get("pauses", "")))
    else:
        if not a.code:
            ap.error("--code is required (or use --batch)")
        text = Path(a.text_file).read_text(encoding="utf-8") if a.text_file else a.text
        if not text:
            ap.error("give --text or --text-file")
        jobs.append((a.code, text, a.pauses))
    results = []
    with ThreadPoolExecutor(max_workers=max(1, a.workers)) as ex:
        for r in ex.map(lambda j: one(a, *j), jobs):
            print(json.dumps(r, ensure_ascii=False), flush=True)
            results.append(r)
    if any(r["http_status"] in (0, 401) for r in results):
        sys.exit(2)
    if any(r["http_status"] == 402 for r in results):
        sys.exit(3)
    sys.exit(0 if all(r["ok"] for r in results) else 1)


if __name__ == "__main__":
    main()
