# ws_package/bootstrap_tcp.py
#
# Startup sequence
# ----------------
# 1. bootstrap_tcp() is called at the very top of ws_engine.main(), before any
#    heavy imports (boto3, SQLAlchemy).
# 2. A daemon thread starts the IPC server (Named Pipe on Windows, TCP on
#    Linux/macOS).  The server is typically ready within 50–100 ms.
# 3. bootstrap_tcp() blocks the caller only until bind/listen succeeds (usually
#    tens of ms), then returns — so start_electron_communication() never races
#    a half-started IPC server.
# 4. Heavy imports in ws_engine.main() then run with IPC already listening.

import threading
import time
import platform

from ws_package.log.writer import LogWriter
from ws_package.utils.error_codes import ErrorCode

logger = LogWriter.file(__name__)

_started = False
_ipc_ready = threading.Event()

# Max time to wait for the IPC server to bind/listen (or create the Named Pipe).
# 5 s is ample — a healthy bind takes <200 ms.  If it hasn't happened in 5 s
# something is actively blocking it (antivirus, another instance, firewall) and
# waiting longer just delays the error the user needs to see.
IPC_BIND_WAIT_SEC = 5.0


def bootstrap_tcp():
    """
    Start the IPC server in a daemon thread and wait until it is listening.

    Waits up to IPC_BIND_WAIT_SEC for bind/listen (or Windows pipe) so callers
    such as start_electron_communication() never observe is_ipc_ready() == False
    solely due to thread scheduling.
    """
    global _started
    if _started:
        return
    _started = True

    def _starter():
        try:
            from ws_package.utils.ipc_server import start_ipc_server, get_ipc_server, is_ipc_ready
            start_ipc_server()
            # start_ipc_server() blocks internally until the server socket is
            # bound and listening — no artificial sleep needed here.
            server = get_ipc_server()
            if server and is_ipc_ready():
                _ipc_ready.set()
                transport = "Named Pipe" if platform.system() == "Windows" else "TCP port 54321"
                logger.info("[BOOTSTRAP] IPC server ready (%s)", transport)
            else:
                logger.warning("[BOOTSTRAP] IPC server started but is_ipc_ready() returned False")
        except Exception as e:
            logger.critical("%s: %s", ErrorCode.IPC_SERVER_FAILED, e, exc_info=True)

    t = threading.Thread(target=_starter, name="IPCBootstrap", daemon=True)
    t.start()
    logger.info("[BOOTSTRAP] IPC bootstrap thread started")

    # Block only until the server thread has finished bind/listen (or pipe is up).
    # This removes the race where start_electron_communication() runs on the main
    # thread while is_ipc_ready() is still false, which produced false
    # "IPC server is not running! bootstrap_tcp failed" and immediate IPC_SERVER_FAILED exits.
    if _ipc_ready.wait(timeout=IPC_BIND_WAIT_SEC):
        logger.info("[BOOTSTRAP] IPC server ready (wait completed)")
    else:
        logger.error(
            "%s — IPC server did not become ready within %.1fs. "
            "Check port 54321 (Linux/macOS), named pipe (Windows), or prior instance on UDP 12345.",
            ErrorCode.IPC_SERVER_FAILED, IPC_BIND_WAIT_SEC,
        )

