"""
This program has been developed by students from the bachelor Computer Science at Utrecht
University within the Software Project course.
© Copyright Utrecht University (Department of Information and Computing Sciences)
"""

import asyncio
import json
import logging

import zmq.asyncio
from fastapi import APIRouter, Request
from fastapi.responses import StreamingResponse
from zmq.asyncio import Context, Socket

from control_backend.core.config import settings
from control_backend.schemas.ri_message import GestureCommand, SpeechCommand

logger = logging.getLogger(__name__)

router = APIRouter()


@router.post("/command/speech", status_code=202)
async def receive_command_speech(command: SpeechCommand, request: Request):
    """
    Send a direct speech command to the robot.

    Publishes the command to the internal 'command' topic. The
    :class:`~control_backend.agents.actuation.robot_speech_agent.RobotSpeechAgent`
    will forward this to the robot.

    :param command: The speech command payload.
    :param request: The FastAPI request object.
    """
    topic = b"command"

    pub_socket: Socket = request.app.state.endpoints_pub_socket
    await pub_socket.send_multipart([topic, command.model_dump_json().encode()])

    return {"status": "Speech command received"}


@router.post("/command/gesture", status_code=202)
async def receive_command_gesture(command: GestureCommand, request: Request):
    """
    Send a direct gesture command to the robot.

    Publishes the command to the internal 'command' topic. The
    :class:`~control_backend.agents.actuation.robot_speech_agent.RobotGestureAgent`
    will forward this to the robot.

    :param command: The speech command payload.
    :param request: The FastAPI request object.
    """
    topic = b"command"

    pub_socket: Socket = request.app.state.endpoints_pub_socket
    await pub_socket.send_multipart([topic, command.model_dump_json().encode()])

    return {"status": "Gesture command received"}


@router.get("/ping_check")
async def ping(request: Request):
    """
    Simple HTTP ping endpoint to check if the backend is reachable.
    """
    pass


@router.get("/commands/gesture/tags")
async def get_available_gesture_tags(request: Request, count=0):
    """
    Endpoint to retrieve the available gesture tags for the robot.

    :param request: The FastAPI request object.
    :return: A list of available gesture tags.
    """
    req_socket = Context.instance().socket(zmq.REQ)
    req_socket.connect(settings.zmq_settings.internal_gesture_rep_adress)

    # Check to see if we've got any count given in the query parameter
    amount = count or None
    timeout = 5  # seconds

    await req_socket.send(f"{amount}".encode() if amount else b"None")
    try:
        body = await asyncio.wait_for(req_socket.recv(), timeout=timeout)
    except TimeoutError:
        body = '{"tags": []}'
        logger.debug("Got timeout error fetching gestures.")

    # Handle empty response and JSON decode errors
    available_tags = []
    if body:
        try:
            available_tags = json.loads(body).get("tags", [])
        except json.JSONDecodeError as e:
            logger.error(f"Failed to parse gesture tags JSON: {e}, body: {body}")
            # Return empty list on JSON error
            available_tags = []

    return {"available_gesture_tags": available_tags}


@router.get("/ping_stream")
async def ping_stream(request: Request):
    """
    SSE endpoint for monitoring the Robot Interface connection status.

    Subscribes to the internal 'ping' topic (published by the RI Communication Agent)
    and yields status updates to the client.

    :return: A StreamingResponse of connection status events.
    """

    async def event_stream():
        # Set up internal socket to receive ping updates

        sub_socket = Context.instance().socket(zmq.SUB)
        sub_socket.connect(settings.zmq_settings.internal_sub_address)
        sub_socket.setsockopt(zmq.SUBSCRIBE, b"ping")
        connected = False

        ping_frequency = settings.behaviour_settings.sleep_s + 1

        # Even though its most likely the updates should alternate
        # (So, True - False - True - False for connectivity),
        # let's still check.
        while True:
            try:
                topic, body = await asyncio.wait_for(
                    sub_socket.recv_multipart(), timeout=ping_frequency
                )
                connected = json.loads(body)
            except TimeoutError:
                logger.debug("got timeout error in ping loop in ping router")
                connected = False

            # Stop if client disconnected
            if await request.is_disconnected():
                logger.info("Client disconnected from SSE")
                break

            connectedJson = json.dumps(connected)
            yield (f"data: {connectedJson}\n\n")

    return StreamingResponse(event_stream(), media_type="text/event-stream")
