import logging
import time

import zmq
from messagebus import Message, DefaultToken, TokenCreator, TokenPriority
from zeromqmessagehandler import MessageDecoder

logger = logging.getLogger("ZeroMqEventManager.StartupGuard")

SERVICE_LOCATOR_GROUP = "3"
TK_SERVICE_LOCATOR_LOOKUP_SERVICE = TokenCreator.create_token(
    TokenPriority.high, SERVICE_LOCATOR_GROUP, "2",
)


class StartupGuard(object):
    """Prevents split-brain by waiting for any existing EventManager to disappear.

    On startup, queries the ServiceLocator for a registered "EventManager".
    If found, pings it to check if it's still alive. If alive, polls until
    it goes away (up to max_wait seconds). If unreachable (stale entry),
    proceeds immediately.
    """

    def __init__(
        self,
        context: zmq.Context,
        locator_address: str,
        service_name: str,
        max_wait: int = 60,
        poll_interval: int = 2,
        ping_timeout: int = 3000,
    ):
        self.context = context
        self.locator_address = locator_address
        self.service_name = service_name
        self.max_wait = max_wait
        self.poll_interval = poll_interval
        self.ping_timeout = ping_timeout

    def wait_for_predecessor(self):
        """Block until no reachable EventManager is registered."""
        logger.info(
            "Checking for existing EventManager (locator=%s, max_wait=%ds)",
            self.locator_address, self.max_wait,
        )

        deadline = time.monotonic() + self.max_wait
        attempt = 0

        while time.monotonic() < deadline:
            attempt += 1
            em_address = self._lookup_event_manager()

            if em_address is None:
                logger.info("No EventManager registered. Proceeding with startup.")
                return

            logger.info(
                "Found existing EventManager at %s (attempt %d). Checking reachability...",
                em_address, attempt,
            )

            if not self._is_reachable(em_address):
                logger.info(
                    "EventManager at %s is unreachable (stale entry). Proceeding with startup.",
                    em_address,
                )
                return

            remaining = deadline - time.monotonic()
            if remaining <= 0:
                break

            logger.warning(
                "EventManager at %s is still alive. Waiting %ds (%.0fs remaining)...",
                em_address, self.poll_interval, remaining,
            )
            time.sleep(self.poll_interval)

        logger.warning(
            "Timed out waiting for predecessor EventManager to disappear after %ds. "
            "Proceeding with startup — split-brain risk if predecessor is still running.",
            self.max_wait,
        )

    def _lookup_event_manager(self):
        """Query ServiceLocator for an existing EventManager. Returns address or None."""
        socket = self.context.socket(zmq.REQ)
        socket.setsockopt(zmq.LINGER, 0)
        try:
            socket.connect(self.locator_address)

            message = Message(
                TK_SERVICE_LOCATOR_LOOKUP_SERVICE,
                data=self.service_name.encode("utf-8"),
                timeout=5000,
            )
            data = MessageDecoder.format_message(message)
            socket.send(data)

            ready = socket.poll(5000, zmq.POLLIN)
            if (ready & zmq.POLLIN) == 0:
                return None

            response_data = socket.recv()
            response = MessageDecoder.parse_message(response_data)
            if response is None or response.token != DefaultToken.TK_SYS_ACK:
                return None

            address = response.data.decode("utf-8")
            if not address:
                return None

            return address
        except Exception:
            logger.debug("ServiceLocator lookup failed", exc_info=True)
            return None
        finally:
            socket.close(linger=0)

    def _is_reachable(self, address):
        """Send a lightweight ping to the EventManager address. Returns True if it responds."""
        socket = self.context.socket(zmq.REQ)
        socket.setsockopt(zmq.LINGER, 0)
        try:
            socket.connect(address)

            # Send a lookup for a non-existent service as a health check.
            # Any response (ACK or NAK) means the EM is alive.
            message = Message(
                TK_SERVICE_LOCATOR_LOOKUP_SERVICE,
                data=b"__startup_guard_ping__",
                timeout=self.ping_timeout,
            )
            data = MessageDecoder.format_message(message)
            socket.send(data)

            ready = socket.poll(self.ping_timeout, zmq.POLLIN)
            if (ready & zmq.POLLIN) == 0:
                return False

            socket.recv()
            return True
        except Exception:
            return False
        finally:
            socket.close(linger=0)