#!/usr/bin/env python3
"""MetaSoul EPU4 REST example, Python 3.9+.

Install: python -m pip install requests
Run: EPU_SECRET=... EPU_ID=... python python-example.py
For realtime MDAD rather than polling, see javascript-example.mjs or
subscribe with a Python Socket.IO client to https://emohuman.com/<epuid>.

This is a one-shot example. For long-running applications, the current gateway
defaults to 60 minutes of REST inactivity and tokens have a separate 24-hour
maximum lifetime. Incoming Socket.IO frames do not renew REST authentication.
An opt-in GET /mdad after 55 minutes without other authenticated REST calls
can keep an intentional session active; it may consume a cached HTTP frame or
wait for the next one. Do not reserve forgotten EPUIDs indefinitely. On HTTP
401, obtain a new token through login. HTTP 503 for EPU_NOT_CONNECTED does not
by itself mean the Bearer session expired.
"""
import os
import math
import requests

BASE = "https://emohuman.com"
EMOTIONS = [
    "Excite", "Confident", "Happy", "Trust", "Desire", "Fear",
    "Surprise", "Inattention", "Sad", "Nostalgia", "Disgust", "Anger"
]


def decode_mdad(mdad):
    if (not isinstance(mdad, list) or len(mdad) < 120 or
            any(type(v) not in (int, float) or not math.isfinite(v) for v in mdad)):
        raise ValueError("Expected at least 120 numeric MDAD values (123 observed live)")
    level_indexes = [9] + [4 + 10 * i for i in range(12)] + [121, 122]
    invalid_levels = [
        {"index": i, "raw": mdad[i]} for i in level_indexes
        if i < len(mdad) and not (0 <= mdad[i] <= 100)
    ]

    def level(i):
        return mdad[i] if i < len(mdad) and 0 <= mdad[i] <= 100 else None

    return {
        "apex": {name: level(4 + 10 * i) for i, name in enumerate(EMOTIONS)},
        "optimistic": level(9),
        # Legacy extra channels are available only if actually received.
        "pulse_speed": mdad[120] if len(mdad) > 120 else None,
        "frustration_satisfaction": level(121),
        "pain_pleasure": level(122),
        "temperature": mdad[123] if len(mdad) > 123 else None,
        "invalid_levels": invalid_levels,  # Retain raw mdad for diagnosis.
    }


def main():
    secret, epuid = os.environ.get("EPU_SECRET"), os.environ.get("EPU_ID")
    if not secret or not epuid:
        raise SystemExit("Set EPU_SECRET and EPU_ID environment variables first")

    with requests.Session() as session:
        login_response = session.post(
            BASE + "/user_login", json={"secret": secret, "epuid": epuid}, timeout=20
        )
        login_response.raise_for_status()
        login = login_response.json()
        if login.get("reply") != "ok" or not login.get("token"):
            raise RuntimeError("Login was not accepted")

        session.headers.update({"Authorization": "Bearer " + login["token"]})
        derive_response = session.post(
            BASE + "/derive", json={"text": "You have an amazing voice"}, timeout=20
        )
        if derive_response.status_code == 401:
            raise RuntimeError("Bearer session expired or invalid. Log in again.")
        derive_response.raise_for_status()
        derive = derive_response.json()
        if derive.get("reply") != "ok":
            raise RuntimeError("Text update was not accepted")
        print("Text submitted. EPU MDAD update triggered.")

        # Important: /mdad waits for the NEXT streamed frame. Calling it now
        # may miss the frame emitted by the preceding /derive request.
        # For deterministic results subscribe to Socket.IO BEFORE /derive.
        print("Waiting for the next MDAD frame (may require another update)...")
        mdad_response = session.get(BASE + "/mdad", timeout=12)
        if mdad_response.status_code == 401:
            raise RuntimeError("Bearer session expired or invalid. Log in again.")
        mdad_response.raise_for_status()
        result = mdad_response.json()
        if result.get("reply") == "ok" and isinstance(result.get("mdad"), list):
            from pprint import pprint
            pprint(decode_mdad(result["mdad"]))
        else:
            print("No MDAD array in the response. The API guide does not define timeout details.")


if __name__ == "__main__":
    main()
