import argparse
import os
import sys

# Add bundled dependencies to path (if packaged with vendor/ directory)
_vendor_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "vendor")
if os.path.isdir(_vendor_dir):
    sys.path.insert(0, _vendor_dir)

import threading
from time import sleep

import uvicorn

from operator_hmi_app import OperatorHMIApp
from web_server import create_app

DEFAULT_GRPC_TARGET = "localhost:5000"
DEFAULT_WEB_PORT = 8080


def _safe_print(*args, **kwargs):
    """print() that never raises.

    When RobotControl (which launched us and owns the read end of our stdout
    pipe) is killed, writing to stdout raises BrokenPipeError. The shutdown path
    must not depend on printing succeeding, so swallow any such error.
    """
    try:
        print(*args, **kwargs)
    except Exception:
        pass


def parse_args():
    parser = argparse.ArgumentParser(description="OperatorHMI for igus robot control")
    parser.add_argument("grpc_target", nargs="?", default=DEFAULT_GRPC_TARGET,
                        help=f"gRPC host:port (default: {DEFAULT_GRPC_TARGET})")
    parser.add_argument("web_port", nargs="?", type=int, default=DEFAULT_WEB_PORT,
                        help=f"Web server port (default: {DEFAULT_WEB_PORT})")
    parser.add_argument("--keep-alive", action="store_true",
                        help="Do not exit when the robot connection is lost; keep "
                             "retrying to (re)connect instead. Useful for "
                             "development. By default the app exits on connection "
                             "loss so RobotControl can start a single fresh instance.")
    return parser.parse_args()


def start_web_server(fastapi_app, port):
    """Starts uvicorn in a daemon thread and returns the server instance."""
    server = uvicorn.Server(
        uvicorn.Config(fastapi_app, host="0.0.0.0", port=port, log_level="info")
    )
    thread = threading.Thread(target=server.run, daemon=True)
    thread.start()
    return server


def run_until_disconnect(app, server):
    """Default: connect once, then exit when the connection is lost.

    On connection loss (e.g. the RobotControl core is stopped) the process must
    not linger, otherwise restarting RobotControl spawns an additional app
    instance on top of this one. Exiting lets RobotControl start a single fresh
    instance on its next launch. This mirrors the example apps' app.py loop.
    """
    app.Connect()
    print("Connected to robot control")
    app.StartPolling()

    while app.IsConnected():
        sleep(0.5)

    _safe_print("Connection to robot control lost. Shutting down.")


def run_with_reconnect(app, server):
    """--keep-alive: retry the initial connect and reconnect on loss.

    Never exits on its own. Intended for development so the process stays up
    across robot restarts.
    """
    while not server.should_exit:
        try:
            app.Connect()
            print("Connected to robot control")
            app.StartPolling()
            break
        except Exception as e:
            print(f"Connection failed: {e}. Retrying in 3s...")
            sleep(3)

    while not server.should_exit:
        if app.IsConnected():
            sleep(0.5)
        else:
            app.StopPolling()
            print("Connection lost. Retrying in 3s...")
            sleep(3)
            try:
                app.Connect()
                print("Reconnected to robot control")
                app.StartPolling()
            except Exception as e:
                print(f"Reconnection failed: {e}")


def main():
    args = parse_args()

    app = OperatorHMIApp("OperatorHMI", args.grpc_target)
    fastapi_app = create_app(app)
    server = start_web_server(fastapi_app, args.web_port)

    print(f"Web server running on http://0.0.0.0:{args.web_port}")

    try:
        if args.keep_alive:
            run_with_reconnect(app, server)
        else:
            run_until_disconnect(app, server)
    except KeyboardInterrupt:
        _safe_print("Shutting down...")
    except Exception as e:
        _safe_print(f"Connection failed: {e}. Shutting down.")
    finally:
        app.StopPolling()
        server.should_exit = True
        app.Disconnect()
        _safe_print("OperatorHMI app stopped")

    # Guarantee termination. gRPC, uvicorn and asyncio can leave lingering
    # threads that may keep the interpreter alive on some platforms. The robot
    # connection is gone and RobotControl needs this process to fully exit so it
    # can spawn a single fresh instance, so force it after best-effort cleanup.
    sys.stdout.flush()
    sys.stderr.flush()
    os._exit(0)


if __name__ == "__main__":
    main()
