import json
import socket
import ssl
import time
from urllib.parse import urlencode

import requests
import websocket

# === PUSHER CONFIG ===
PUSHER_APP_KEY = "bbaaf618b8257bd9e819"
PUSHER_CLUSTER = "ap1"
CHANNEL_NAME = "attendance-users"
EVENT_NAME = "attendance.user.add"

# === DEVICE CONFIG ===
DEVICE_IP = "192.168.1.98"
COMM_KEY = "4370"
DEFAULT_GROUP = "1"
DEFAULT_CARD = "0"
DEFAULT_PRIVILEGE = "0"

# === CALLBACK API ===
CONFIRM_URL = "http://192.168.1.2:8000/api/attendance/users/confirm"
API_TOKEN = "CHANGE_ME"

TIMEOUT = 5


def parse_data(data: str, start: str, end: str) -> str:
    try:
        s = data.index(start) + len(start)
        e = data.index(end, s)
        return data[s:e]
    except ValueError:
        return ""


def send_soap(xml: str) -> str:
    req = (
        "POST /iWsService HTTP/1.0\r\n"
        "Content-Type: text/xml\r\n"
        f"Content-Length: {len(xml)}\r\n\r\n"
        f"{xml}\r\n"
    )
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(TIMEOUT)
    s.connect((DEVICE_IP, 80))
    s.sendall(req.encode("utf-8"))

    response = b""
    while True:
        chunk = s.recv(1024)
        if not chunk:
            break
        response += chunk
    s.close()
    return response.decode(errors="ignore")


def set_user(pin: str, name: str) -> str:
    xml = (
        f"<SetUserInfo><ArgComKey Xsi:type=\"xsd:integer\">{COMM_KEY}</ArgComKey>"
        f"<Arg>"
        f"<PIN>{pin}</PIN>"
        f"<Name>{name}</Name>"
        f"<Privilege>{DEFAULT_PRIVILEGE}</Privilege>"
        f"<Password></Password>"
        f"<Group>{DEFAULT_GROUP}</Group>"
        f"<Card>{DEFAULT_CARD}</Card>"
        f"</Arg></SetUserInfo>"
    )
    text = send_soap(xml)
    info = parse_data(text, "<Information>", "</Information>")
    return info or "OK"


def confirm(request_id: int, pin: str, name: str, status: str, message: str | None = None):
    payload = {
        "request_id": request_id,
        "pin": pin,
        "name": name,
        "status": status,
        "message": message,
    }
    headers = {
        "Content-Type": "application/json",
        "Accept": "application/json",
        "X-Attendance-Token": API_TOKEN,
    }
    requests.post(CONFIRM_URL, json=payload, headers=headers, timeout=10)


def on_open(ws):
    print("[WS] Connected. Subscribing channel...")
    ws.send(json.dumps({
        "event": "pusher:subscribe",
        "data": {"channel": CHANNEL_NAME}
    }))


def on_message(ws, message):
    try:
        msg = json.loads(message)
    except Exception:
        return

    ev = msg.get("event")
    data = msg.get("data")

    if ev == "pusher:connection_established":
        info = json.loads(data)
        print(f"[WS] connection_established socket_id={info.get('socket_id')}")
        return

    if ev == "pusher_internal:subscription_succeeded":
        print(f"[WS] subscribed ok to {CHANNEL_NAME}")
        return

    if ev != EVENT_NAME:
        return

    if isinstance(data, str):
        try:
            data = json.loads(data)
        except Exception:
            pass

    request_id = data.get("requestId") or data.get("request_id")
    pin = data.get("pin")
    name = data.get("name")
    action = (data.get("action") or "add").lower()

    if not request_id or not pin or not name:
        return

    try:
        info = set_user(str(pin), str(name))
        confirm(int(request_id), str(pin), str(name), "success", info)
        print(f"[USER {action.upper()}] OK pin={pin} name={name}")
    except Exception as e:
        confirm(int(request_id), str(pin), str(name), "failed", str(e))
        print(f"[USER {action.upper()}] FAILED pin={pin} name={name} err={e}")


def on_error(ws, error):
    print("[WS] Error:", error)


def on_close(ws, code, msg):
    print(f"[WS] Closed: code={code} msg={msg}")


def build_ws_url():
    qs = urlencode({
        "protocol": "7",
        "client": "python",
        "version": "1.0",
        "flash": "false"
    })
    return f"wss://ws-{PUSHER_CLUSTER}.pusher.com/app/{PUSHER_APP_KEY}?{qs}"


def main():
    url = build_ws_url()
    print("[INIT] URL:", url)
    print("[INIT] Channel:", CHANNEL_NAME, "Event:", EVENT_NAME)

    while True:
        try:
            ws = websocket.WebSocketApp(
                url,
                on_open=on_open,
                on_message=on_message,
                on_error=on_error,
                on_close=on_close,
            )
            ws.run_forever(sslopt={"cert_reqs": ssl.CERT_REQUIRED})
        except KeyboardInterrupt:
            print("Bye.")
            break
        except Exception as e:
            print("[WS] Crash, reconnect in 3s:", e)
            time.sleep(3)


if __name__ == "__main__":
    main()

# aktifkan trace supaya keluar error detail
websocket.enableTrace(True)

def on_error(ws, error):
    print("[WS] Error detail:", repr(error))

def on_close(ws, code, msg):
    print(f"[WS] Closed: code={code} msg={msg}")
