#!/usr/bin/env python3
import argparse
import json
import threading

import uvicorn

from ros_sugar.launch.executable import run_component, setup_component
from ros_sugar.ui_node import UINode, UINodeConfig
from ros_sugar.ui_node.api import build_api_app
from ros_sugar.ui_node.api_utils import server_config
from ros_sugar.ui_node.security import (
    ApiKeyError,
    ApiKeys,
    CertificateError,
    banner,
    resolve_certificate,
    session_key,
    state_dir,
)
from ros_sugar.utils import logger


def main():
    """
    Run the UI node: a ROS node plus the JSON/WebSocket API server.

    Optionally run the FastHTML browser UI if dependencies installed.
    """
    logger.info("Starting UI Node executable")

    # Start UI helper ROS node in a separate thread
    ros_node: UINode = setup_component(
        list_of_components=[UINode], list_of_configs=[UINodeConfig]
    )  # type: ignore

    ros_node_config = ros_node.config

    # Get parsed UI elements
    parser = argparse.ArgumentParser()
    parser.add_argument("--ui_input_elements", type=str)
    parser.add_argument("--ui_output_elements", type=str)
    parser.add_argument("--ui_task_elements", type=str)
    parser.add_argument("--ui_service_clients", type=str)
    parser.add_argument("--system_info", type=str)
    args, _ = parser.parse_known_args()  # type: ignore
    additional_input_elements = (
        json.loads(args.ui_input_elements) if args.ui_input_elements else None
    )
    additional_output_elements = (
        json.loads(args.ui_output_elements) if args.ui_output_elements else None
    )
    additional_task_elements = (
        json.loads(args.ui_task_elements) if args.ui_task_elements else None
    )
    if args.ui_service_clients:
        ros_node._client_inputs_json = args.ui_service_clients

    system_info = json.loads(args.system_info) if args.system_info else None

    # A secure UI does not fall back to plain HTTP, or to an API without keys
    certificate = keys = browser_session_key = None
    valid_keys = []
    security_error = None
    if ros_node_config.secure:
        try:
            certificate = resolve_certificate()
            keys = ApiKeys(state_dir())
            valid_keys = [key for key in keys.all() if not key.expired()]
            browser_session_key = session_key(state_dir())
        except (CertificateError, ApiKeyError, OSError) as e:
            security_error = e

    # Spin the ROS node in a background thread
    ros_thread = threading.Thread(
        target=run_component,
        args=[ros_node],
        daemon=True,
    )
    ros_thread.start()

    if security_error is not None:
        # The node stays up, so the recipe's other components are still
        # activated. Only the UI is not served
        ros_node.get_logger().error(f"The UI is not served: {security_error}")
        ros_thread.join()
        return

    # Build the optional FastHTML browser app. Mounts under "/"
    if not ros_node_config.serve_browser:
        logger.info("Browser UI disabled. Serving the JSON/WebSocket API only.")
        browser_app = None
    else:
        from ros_sugar.ui_node.browser import build_browser_app

        browser_app = build_browser_app(
            ros_node,
            additional_input_elements=additional_input_elements,
            additional_output_elements=additional_output_elements,
            additional_task_elements=additional_task_elements,
            system_info=system_info,
            session_key=browser_session_key,
        )

    # Create the JSON/WebSocket API is the server host, server host
    api_app = build_api_app(
        ros_node,
        browser_app=browser_app,
        keys=keys,
        session_key=browser_session_key,
    )

    if certificate is not None:
        for line in banner(certificate):
            ros_node.get_logger().info(line)
        if not valid_keys:
            ros_node.get_logger().info(
                "There are no valid API keys, so API use is not allowed."
                " Create one with: ros2 run automatika_ros_sugar ui_security keys"
                " create --name <name> --scopes read,command"
            )
    else:
        # Not secure
        ros_node.get_logger().warning(
            "The UI is not secure (enable_ui(secure=False)). Use it only for development"
            ". It is served over plain HTTP, and the microphone will not work"
            " when accessing it from another machine."
        )

    protocol = "https" if certificate is not None else "http"
    ros_node.get_logger().info(
        f"Access the recipe UI at: {protocol}://<IP_ADDRESS_OF_THE_ROBOT>:{ros_node_config.port}"
    )

    uvicorn.Server(server_config(api_app, ros_node_config.port, certificate)).run()


if __name__ == "__main__":
    main()
