"""
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)
--------------------------------------------------------------------------------
Control Backend Main Application.

This module defines the FastAPI application that serves as the entry point for the
Control Backend. It manages the lifecycle of the entire system, including:

1.  **Socket Initialization**: Setting up the internal ZeroMQ PUB/SUB proxy for agent communication.
2.  **Agent Management**: Instantiating and starting all agents.
3.  **API Routing**: Exposing REST endpoints for external interaction.

Lifecycle Manager
-----------------
The :func:`lifespan` context manager handles the startup and shutdown sequences:
-   **Startup**: Configures logging, starts the ZMQ proxy, connects sockets, and launches agents.
-   **Shutdown**: Handles graceful cleanup (though currently minimal).
"""

import contextlib
import logging
import threading

import zmq
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from zmq.asyncio import Context

# BDI agents
from control_backend.agents.bdi import (
    BDICoreAgent,
    TextBeliefExtractorAgent,
)
from control_backend.agents.bdi.bdi_program_manager import BDIProgramManager

# Communication agents
from control_backend.agents.communication import RICommunicationAgent

# Emotional Agents
# LLM Agents
from control_backend.agents.llm import LLMAgent

# User Interrupt Agent
from control_backend.agents.user_interrupt.user_interrupt_agent import UserInterruptAgent

# Other backend imports
from control_backend.api.v1.router import api_router
from control_backend.core.config import settings
from control_backend.logging import setup_logging
from control_backend.schemas.program_status import PROGRAM_STATUS, ProgramStatus

logger = logging.getLogger(__name__)


def setup_sockets():
    """
    Initialize and run the internal ZeroMQ Proxy (XPUB/XSUB).

    This proxy acts as the central message bus, forwarding messages published on the
    internal PUB address to all subscribers on the internal SUB address.
    """
    context = Context.instance()

    internal_pub_socket = context.socket(zmq.XPUB)
    internal_pub_socket.bind(settings.zmq_settings.internal_sub_address)
    logger.debug("Internal publishing socket bound to %s", internal_pub_socket)

    internal_sub_socket = context.socket(zmq.XSUB)
    internal_sub_socket.bind(settings.zmq_settings.internal_pub_address)
    logger.debug("Internal subscribing socket bound to %s", internal_sub_socket)
    try:
        zmq.proxy(internal_sub_socket, internal_pub_socket)
    except zmq.ZMQError:
        logger.warning("Error while handling PUB/SUB proxy. Closing sockets.")
    finally:
        internal_pub_socket.close()
        internal_sub_socket.close()


@contextlib.asynccontextmanager
async def lifespan(app: FastAPI):
    """
    Application lifespan context manager to handle startup and shutdown events.
    """
    # --- APPLICATION STARTUP ---
    setup_logging()
    logger.info("%s is starting up.", app.title)

    # Initiate sockets
    proxy_thread = threading.Thread(target=setup_sockets)
    proxy_thread.daemon = True
    proxy_thread.start()

    context = Context.instance()

    endpoints_pub_socket = context.socket(zmq.PUB)
    endpoints_pub_socket.connect(settings.zmq_settings.internal_pub_address)
    app.state.endpoints_pub_socket = endpoints_pub_socket

    await endpoints_pub_socket.send_multipart([PROGRAM_STATUS, ProgramStatus.STARTING.value])

    # --- Initialize Agents ---
    logger.info("Initializing and starting agents.")

    agents_to_start = {
        "RICommunicationAgent": (
            RICommunicationAgent,
            {
                "name": settings.agent_settings.ri_communication_name,
                "address": settings.zmq_settings.ri_communication_address,
                "bind": True,
            },
        ),
        "LLMAgent": (
            LLMAgent,
            {
                "name": settings.agent_settings.llm_name,
            },
        ),
        "BDICoreAgent": (
            BDICoreAgent,
            {
                "name": settings.agent_settings.bdi_core_name,
            },
        ),
        "TextBeliefExtractorAgent": (
            TextBeliefExtractorAgent,
            {
                "name": settings.agent_settings.text_belief_extractor_name,
            },
        ),
        "ProgramManagerAgent": (
            BDIProgramManager,
            {
                "name": settings.agent_settings.bdi_program_manager_name,
            },
        ),
        "UserInterruptAgent": (
            UserInterruptAgent,
            {
                "name": settings.agent_settings.user_interrupt_name,
            },
        ),
    }

    agents = []

    for name, (agent_class, kwargs) in agents_to_start.items():
        try:
            logger.debug("Starting agent: %s", name)
            agent_instance = agent_class(**kwargs)
            await agent_instance.start()
            agents.append(agent_instance)
            logger.info("Agent '%s' started successfully.", name)
        except Exception as e:
            logger.error("Failed to start agent '%s': %s", name, e, exc_info=True)
            raise

    logger.info("Application startup complete.")

    await endpoints_pub_socket.send_multipart([PROGRAM_STATUS, ProgramStatus.RUNNING.value])

    yield

    # --- APPLICATION SHUTDOWN ---
    logger.info("%s is shutting down.", app.title)

    await endpoints_pub_socket.send_multipart([PROGRAM_STATUS, ProgramStatus.STOPPING.value])
    # Additional shutdown logic goes here
    for agent in agents:
        await agent.stop()

    logger.info("Application shutdown complete.")


# if __name__ == "__main__":
app = FastAPI(title=settings.app_title, lifespan=lifespan)

# This middleware allows other origins to communicate with us
app.add_middleware(
    CORSMiddleware,  # https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS
    allow_origins=[settings.ui_url],  # address of our UI application
    allow_methods=["*"],  # GET, POST, etc.
)

app.include_router(api_router, prefix="")  # TODO: make prefix /api/v1


@app.get("/")
async def root():
    return {"status": "ok"}
