# /mod/web/acousticsspace/webhook.py
# -*- coding: utf-8 -*-
from flask import Flask, request, jsonify, Response
import logging, json, time, queue, threading
from datetime import datetime

app = Flask(__name__)

# ──────────────────────────────
# 기본 설정
# ──────────────────────────────
logging.basicConfig(level=logging.INFO)
BUILD_TAG = "no-sign-2025-11-03"

# SSE 클라이언트 관리 (스레드 안전)
_clients = []
_clients_lock = threading.Lock()

def _broadcast_to_clients(payload):
    """연결된 SSE 클라이언트들에게 payload(JSON)를 전송"""
    try:
        msg = "data: " + json.dumps(payload, ensure_ascii=False) + "\n\n"
    except Exception as e:
        app.logger.error("SSE 직렬화 실패: %s", e)
        return
    with _clients_lock:
        for q in list(_clients):
            try:
                q.put_nowait(msg)
            except Exception:
                # 큐가 꽉 찼거나 끊긴 경우는 조용히 스킵
                pass

def _normalize_item(it: dict) -> dict:
    """Aqara resource_report item 정규화: value를 bool/float로 변환 시도"""
    rid = it.get("resourceId")
    raw = it.get("value")
    val = raw
    if isinstance(raw, str):
        if raw in ("0", "1"):
            val = (raw == "1")
        else:
            try:
                val = float(raw)
            except Exception:
                val = raw
    return {
        "resourceId": rid,
        "value": val,
        "subjectId": it.get("subjectId"),
        "model": it.get("model"),
        "time": it.get("time"),
        "statusCode": it.get("statusCode"),
        "triggerSource": it.get("triggerSource"),
    }

# ──────────────────────────────
# Healthcheck
# ──────────────────────────────
@app.route("/aqara/health", methods=["GET"])
def health():
    return jsonify({
        "ok": True,
        "build": BUILD_TAG,
        "server_time": datetime.utcnow().isoformat() + "Z"
    })

# ──────────────────────────────
# Webhook (HTTP Push 수신)
#  - 서명 검증 OFF (테스트/개발용)
#  - 어떤 바디/헤더든 수용하고 즉시 {"code":0} 응답
#  - resource_report 형태면 값 추출해서 로그 + SSE 브로드캐스트
# ──────────────────────────────
@app.route("/aqara/webhook", methods=["POST"])
def aqara_webhook():
    # 빈 바디/비JSON도 허용
    data = request.get_json(silent=True)
    if data is None:
        raw = request.get_data(cache=False, as_text=True)
        app.logger.info("Aqara PUSH (raw): headers=%s raw=%s",
                        dict(request.headers), raw)
        _broadcast_to_clients({"raw": raw, "ts": int(time.time())})
        return jsonify({"code": 0, "build": BUILD_TAG})

    app.logger.info("Aqara PUSH (json): %s", json.dumps(data, ensure_ascii=False))

    # msgType / type 중 하나를 지원
    msg_type = data.get("msgType") or data.get("type")

    if msg_type == "resource_report":
        items = data.get("data", [])
        normed = [_normalize_item(x) for x in items]
        for n in normed:
            app.logger.info("→ resourceId=%s value=%s model=%s subjectId=%s",
                            n.get("resourceId"), n.get("value"), n.get("model"), n.get("subjectId"))
        _broadcast_to_clients({"event": "resource_report", "items": normed, "ts": int(time.time())})
    else:
        # 그 외 타입도 통째로 브로드캐스트
        _broadcast_to_clients({"event": msg_type, **data, "ts": int(time.time())})

    # Aqara는 빠른 응답을 요구
    return jsonify({"code": 0, "build": BUILD_TAG})

# ──────────────────────────────
# SSE 엔드포인트 (앱에서 실시간 수신)
#  - URL: /aqara/events
#  - 연결 유지, 주기 heartbeat
# ──────────────────────────────
@app.route("/aqara/events", methods=["GET"])
def sse_events():
    q = queue.Queue(maxsize=200)
    with _clients_lock:
        _clients.append(q)

    def stream():
        # 재연결 힌트(3초)
        yield "retry: 3000\n\n"
        # 최초 인사/빌드 태그
        hello = {"ok": True, "build": BUILD_TAG, "ts": int(time.time())}
        yield "event: hello\ndata: " + json.dumps(hello) + "\n\n"

        last_ping = time.time()
        try:
            while True:
                try:
                    msg = q.get(timeout=10)
                    yield msg
                except queue.Empty:
                    # 10초마다 heartbeat
                    if time.time() - last_ping >= 10:
                        yield "event: ping\ndata: {}\n\n"
                        last_ping = time.time()
        except GeneratorExit:
            # 클라이언트 종료 정리
            with _clients_lock:
                try:
                    _clients.remove(q)
                except ValueError:
                    pass

    headers = {
        "Content-Type": "text/event-stream",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        # CORS가 필요하면 아래 한 줄을 상황에 맞게 열어주세요.
        # "Access-Control-Allow-Origin": "*",
    }
    return Response(stream(), headers=headers)

# ──────────────────────────────
# 개발용 단독 실행 (운영은 mod_wsgi 권장)
# ──────────────────────────────
if __name__ == "__main__":
    # 개발 중에는 debug=True 로 자동 리로드 가능
    app.run(host="0.0.0.0", port=5000, debug=True)
