"""
Electron Communication Module (IPC abstraction)
Handles communication with Electron app via IPC (Named Pipes on Windows, TCP on Linux/macOS).
This version uses the IPC abstraction layer which automatically selects the appropriate transport.
It also sends a 'ready' message immediately when a client connects to avoid race conditions.
"""
import json
import threading
import time
from typing import Optional, Dict, Any

from ws_package.log.writer import LogWriter
from ws_package.utils.ipc_server import get_ipc_server, stop_ipc_server, is_ipc_ready, is_connected, send_message, get_message

logger = LogWriter.file(__name__)

# Python app version (reported to Electron; no longer required to match Electron version)
# Import from ws_engine to ensure single source of truth
try:
    from ws_package.ws_engine import PYTHON_APP_VERSION
except ImportError:
    # Fallback if import fails (shouldn't happen in normal operation)
    PYTHON_APP_VERSION = "1.2"


class ElectronCommunicator:
    """Handles communication with Electron app via IPC (Named Pipes on Windows, TCP on Linux/macOS)."""

    def __init__(self):
        self.running = False
        self.ipc_server = None
        self._ready_sent_on_connect = False
        self._ipc_ready_watcher_thread: Optional[threading.Thread] = None
        self._last_ready_sent_at: float = 0.0

    def start(self):
        """Start using the global IPC server (do NOT start it here)."""
        if self.running:
            logger.warning("Electron communicator already running")
            return

        self.running = True

        try:
            # IPC server must be started by bootstrap at Python startup.
            self.ipc_server = get_ipc_server()
            if not self.ipc_server or not is_ipc_ready():
                logger.error("IPC server is not running! bootstrap_tcp failed or not invoked.")
                # keep running == True so callers can still attempt to use communicator,
                # but most operations will be no-ops until IPC server is available.
            else:
                logger.info("Electron communicator is now using existing IPC server")

            # Start watcher to send 'ready' exactly when a client connects.
            # This avoids stdout fallback races and ensures Electron receives the ready quickly.
            self._start_ready_on_connect_watcher()

        except Exception as e:
            logger.error("Failed to initialize Electron communicator: %s", e, exc_info=True)
            self.running = False
            raise

    def _start_ready_on_connect_watcher(self):
        """Start a background watcher thread that detects IPC connection events.

        Sends a ``status_update(ipc_connected)`` message to Electron each time
        a new client connects, then resets the flag on disconnect so the message
        is re-sent on the next connection.
        """
        # Avoid starting multiple watcher threads
        if self._ipc_ready_watcher_thread and self._ipc_ready_watcher_thread.is_alive():
            return

        def _watcher():
            try:
                logger.debug("IPC-ready watcher started: continuously watching for Electron client connections...")
                last_connected_state = False
                
                # Continuously watch for connections (not just once)
                while self.running:
                    if self.ipc_server:
                        connected = is_connected()
                        
                        # Detect new connection (transition from disconnected to connected)
                        if connected and not last_connected_state:
                            # New connection detected - send status_update (not "ready") on IPC connect.
                            # The authoritative "ready" is sent only after authentication completes.
                            now = time.time()
                            if not self._ready_sent_on_connect and (now - self._last_ready_sent_at > 2.0):
                                try:
                                    self.send_to_electron("status_update", {
                                        "stage": "ipc_connected",
                                        "message": "WS_Engine IPC channel established — authentication in progress",
                                        "version": PYTHON_APP_VERSION
                                    })
                                    logger.info("✓ Sent status_update(ipc_connected) to Electron on new IPC connection")
                                    self._last_ready_sent_at = time.time()
                                except Exception as e:
                                    logger.warning("Failed to send IPC connect status_update: %s", e)
                                finally:
                                    self._ready_sent_on_connect = True
                        
                        # Detect disconnection (transition from connected to disconnected)
                        if not connected and last_connected_state:
                            # Client disconnected - reset flag so we send ready on next connection
                            logger.debug("IPC client disconnected - resetting ready flag for next connection")
                            self._ready_sent_on_connect = False
                        
                        last_connected_state = connected
                    
                    # Small sleep to keep CPU usage low
                    time.sleep(0.05)  # Check every 50ms
                    
            except Exception as e:
                logger.exception("Exception in IPC-ready watcher: %s", e)

        self._ipc_ready_watcher_thread = threading.Thread(
            target=_watcher,
            daemon=True,
            name="IPCReadyWatcher"
        )
        self._ipc_ready_watcher_thread.start()

    def stop(self):
        """Stop the communicator and shutdown the IPC server reference (not the global process)."""
        if not self.running:
            return

        self.running = False

        try:
            # Stop IPC server cleanly (delegates to ipc_server stop routine)
            stop_ipc_server()
            logger.info("Electron communicator stopped and IPC server stopped")
        except Exception as e:
            logger.error("Error stopping IPC server: %s", e, exc_info=True)

    def get_message(self, timeout: Optional[float] = None) -> Optional[Dict[str, Any]]:
        """
        Get a message from Electron via IPC (non-blocking or with timeout).

        Args:
            timeout: Timeout in seconds. If None, perform non-blocking check and return immediately.
                     If 0, block indefinitely. Otherwise, block up to `timeout` seconds.

        Returns:
            Message dictionary or None if no message available / timeout.
        """
        if not self.ipc_server:
            return None

        # Delegate to the IPC server's message queue.
        try:
            # ipc_server.get_message already handles timeout semantics
            return get_message(timeout=timeout)
        except Exception as e:
            logger.error("Error getting message from IPC server: %s", e, exc_info=True)
            return None

    def send_to_electron(self, message_type: str, data: Optional[Dict[str, Any]] = None):
        """
        Send message to Electron via IPC.

        Args:
            message_type: Type of message (e.g., 'idle_popup_trigger', 'activity_update')
            data: Optional data dictionary to send
        """
        if not self.ipc_server:
            logger.warning("Cannot send message - IPC server not available: %s", message_type)
            return False

        # If client not connected, return False quickly (caller can retry or queue if desired)
        if not is_connected():
            logger.debug("Cannot send message - no Electron client connected: %s", message_type)
            return False

        success = send_message(message_type, data)
        if not success:
            logger.debug("Failed to send message to Electron: %s", message_type)
            return False
        return True

    def log_to_electron(self, level: str, message: str):
        """Send a structured log message to Electron via IPC (best-effort).

        Args:
            level: Log level string (e.g. 'info', 'warning', 'error').
            message: Human-readable log message text.
        """
        try:
            self.send_to_electron("log", {
                "level": level,
                "message": message
            })
        except Exception as e:
            # Don't raise; logging to Electron is best-effort
            logger.debug("Failed to log to Electron: %s", e)

    def is_connected(self) -> bool:
        """Check if Electron client is connected via IPC."""
        if not self.ipc_server:
            return False
        return is_connected()


class NullElectronCommunicator:
    """No-op IPC stand-in for build=silent (MDM, no Electron process)."""

    def start(self):
        logger.info("[Silent] Electron IPC disabled — using null communicator")

    def stop(self):
        pass

    def get_message(self, timeout: Optional[float] = None) -> Optional[Dict[str, Any]]:
        return None

    def send_to_electron(self, message_type: str, data: Optional[Dict[str, Any]] = None):
        return False

    def log_to_electron(self, level: str, message: str):
        pass

    def is_connected(self) -> bool:
        return False


# Global instance
electron_comm = ElectronCommunicator()
_silent_ipc_stub_installed = False


def install_silent_ipc_stub() -> ElectronCommunicator:
    """Replace global communicator and skip TCP/pipe server (silent MDM builds)."""
    global electron_comm, _silent_ipc_stub_installed
    electron_comm = NullElectronCommunicator()
    _silent_ipc_stub_installed = True
    electron_comm.start()
    return electron_comm


def is_silent_ipc_stub() -> bool:
    return _silent_ipc_stub_installed


def get_electron_comm() -> ElectronCommunicator:
    """Get the global Electron communicator instance."""
    return electron_comm


def start_electron_communication():
    """Start Electron communication (call this at startup)."""
    electron_comm.start()


def stop_electron_communication():
    """Stop Electron communication (call this at shutdown)."""
    electron_comm.stop()
