"""Lightweight connection manager with lazy imports.

This module avoids importing/initializing the bundled Grifo_E_1553lib at
module-import time. Call `get_manager()` and then `init_library()` from the
GUI when the user requests initialization. Socket binding is performed by
the library's `connection.utils.init_recv_sock()` which is invoked only after
imports succeed.
"""
from threading import Thread
import threading
import time
from typing import Optional
import importlib
import os
import sys


class ConnectionManager:
    def __init__(self):
        self._running = False
        self._imported = False
        self._messages_mod = None
        self._utils_mod = None
        self._sender_thread = None
        self._recv_thread = None
        self.import_error: Optional[str] = None

    def init_library(self) -> bool:
        """Import the bundled Grifo_E_1553lib modules lazily.

        Returns True on success, False on failure. On failure `self.import_error`
        contains a diagnostic string that can be shown to the user.
        """
        # Ensure local package path is on sys.path so subpackage imports work
        try:
            pybus_pkg = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
            if pybus_pkg not in sys.path:
                sys.path.insert(0, pybus_pkg)
        except Exception:
            pass

        # Try several import paths to be robust
        candidates = [
            'Grifo_E_1553lib.messages.messages',
            'pybusmonitor1553.Grifo_E_1553lib.messages.messages',
        ]
        messages_mod = None
        last_exc = None
        for m in candidates:
            try:
                messages_mod = importlib.import_module(m)
                break
            except Exception as e:
                last_exc = e

        if messages_mod is None:
            self.import_error = f"Could not import messages module: {last_exc}"
            return False

        # import utils (connection helpers)
        try:
            utils_mod = importlib.import_module(m.replace('.messages.messages', '.connection.utils'))
        except Exception:
            try:
                utils_mod = importlib.import_module('pybusmonitor1553.Grifo_E_1553lib.connection.utils')
            except Exception as e:
                self.import_error = f"Could not import connection.utils: {e}"
                return False

        self._messages_mod = messages_mod
        self._utils_mod = utils_mod
        self._imported = True
        self.import_error = None
        # Create default MessageNew wrappers (same set as connection.start.start())
        try:
            # Import message classes used to construct wrappers
            MsgRdrSettingsAndParametersTellback = importlib.import_module(
                m.replace('.messages.messages', '.messages.msg_rdr_settings_and_parameters_tellback')
            ).MsgRdrSettingsAndParametersTellback
            MsgRdrStatusTellback = importlib.import_module(
                m.replace('.messages.messages', '.messages.msg_rdr_status_tellback')
            ).MsgRdrStatusTellback
            MsgRdrSettingsAndParameters = importlib.import_module(
                m.replace('.messages.messages', '.messages.msg_rdr_settings_and_parameters')
            ).MsgRdrSettingsAndParameters
            MsgRdrOperationCommand = importlib.import_module(
                m.replace('.messages.messages', '.messages.msg_rdr_operation_command')
            ).MsgRdrOperationCommand
            MsgGraphicSetting = importlib.import_module(
                m.replace('.messages.messages', '.messages.msg_graphic_setting')
            ).MsgGraphicSetting
            MsgNavDataAndCursor = importlib.import_module(
                m.replace('.messages.messages', '.messages.msg_nav_data_and_cursor')
            ).MsgNavDataAndCursor
            MsgInuHighSpeed = importlib.import_module(
                m.replace('.messages.messages', '.messages.msg_inu_high_speed')
            ).MsgInuHighSpeed

            # Instantiate the common wrappers (B6, B7, A1-A5)
            MessageNew = getattr(messages_mod, 'MessageNew')
            # B tellbacks (request messages)
            try:
                MessageNew("B6", MsgRdrSettingsAndParametersTellback, 10, 16, True)
                MessageNew("B7", MsgRdrStatusTellback, 25, 17, True)
            except Exception:
                pass

            # A messages
            try:
                a1 = MessageNew("A1", MsgRdrSettingsAndParameters, 10, 1)
                try:
                    a1.message.settings.set_history_level(importlib.import_module(m.replace('.messages.messages', '.data_types.enums')).TargetHistory.TARGET_HISTORY_LEVEL_04)
                    a1.message.settings.str.rdr_symbology_intensity = 127
                except Exception:
                    pass
                a2 = MessageNew("A2", MsgRdrOperationCommand, 25, 2)
                try:
                    a2.message.rdr_mode_command.set_master_mode(importlib.import_module(m.replace('.messages.messages', '.data_types.enums')).RdrModes.RWS)
                    a2.message.param1.set_gm_submode(0)
                    a2.message.param2.set_spare_0_4(0)
                except Exception:
                    pass
                MessageNew("A3", MsgGraphicSetting, 10, 3)
                a4 = MessageNew("A4", MsgNavDataAndCursor, 50, 4)
                try:
                    a4.message.validity_and_slew.raw = 0
                except Exception:
                    pass
                a5 = MessageNew("A5", MsgInuHighSpeed, 50, 5)
                try:
                    a5.message.timetag.raw = 0
                except Exception:
                    pass
                # Data-link targets A7 / A8
                try:
                    Msg1DataLinkTarget = importlib.import_module(m.replace('.messages.messages', '.messages.msg1_data_link_target')).Msg1DataLinkTarget
                    Msg2DataLinkTarget = importlib.import_module(m.replace('.messages.messages', '.messages.msg2_data_link_target')).Msg2DataLinkTarget
                    MessageNew("A7", Msg1DataLinkTarget, 20, 7)
                    MessageNew("A8", Msg2DataLinkTarget, 20, 8)
                except Exception:
                    pass
                # B-pages (TWS) placeholders at SA 11..15 and beyond. Prefer
                # page-specific ctypes classes where available so the GUI can
                # render distinct field names per page (B1 -> targets 01/02,
                # B2 -> targets 03/04, etc.). Fall back to the 0102 class.
                try:
                    tws_mod = importlib.import_module(m.replace('.messages.messages', '.messages.tws_status_and_targets'))
                    Tws0102 = getattr(tws_mod, 'TwsStatusAndTargets0102', None)
                    Tws0304 = getattr(tws_mod, 'TwsStatusAndTargets0304', None)
                    Tws0506 = getattr(tws_mod, 'TwsStatusAndTargets0506', None)
                    Tws0708 = getattr(tws_mod, 'TwsStatusAndTargets0708', None)
                    Tws0910 = getattr(tws_mod, 'TwsStatusAndTargets0910', None)

                    # mapping for B1..B5 -> page-specific types
                    mapping = {
                        'B1': Tws0102,
                        'B2': Tws0304 or Tws0102,
                        'B3': Tws0506 or Tws0102,
                        'B4': Tws0708 or Tws0102,
                        'B5': Tws0910 or Tws0102,
                    }

                    # Create MessageNew wrappers for B1..B5
                    for lbl, sub in (('B1', 11), ('B2', 12), ('B3', 13), ('B4', 14), ('B5', 15)):
                        cls = mapping.get(lbl) or Tws0102
                        if cls is not None:
                            MessageNew(lbl, cls, 10, sub, True)

                    # For B8..B18 prefer page-specific classes if available
                    b_high_mapping = {
                        'B8': getattr(tws_mod, 'TwsStatusAndTargets1112', None),
                        'B9': getattr(tws_mod, 'TwsStatusAndTargets1314', None),
                        'B10': getattr(tws_mod, 'TwsStatusAndTargets1516', None),
                        'B11': getattr(tws_mod, 'TwsStatusAndTargets1718', None),
                        'B12': getattr(tws_mod, 'TwsStatusAndTargets1920', None),
                        'B13': getattr(tws_mod, 'TwsStatusAndTargets2122', None),
                        'B14': getattr(tws_mod, 'TwsStatusAndTargets2324', None),
                        'B15': getattr(tws_mod, 'TwsStatusAndTargets2526', None),
                        'B16': getattr(tws_mod, 'TwsStatusAndTargets2728', None),
                        'B17': getattr(tws_mod, 'TwsStatusAndTargets2930', None),
                        'B18': getattr(tws_mod, 'TwsStatusAndTargets3132', None),
                    }
                    for lbl, sub in (('B8', 18), ('B9', 19), ('B10', 20), ('B11', 21), ('B12', 22), ('B13', 23), ('B14', 24), ('B15', 25), ('B16', 26), ('B17', 27), ('B18', 28)):
                        cls = b_high_mapping.get(lbl) or Tws0102
                        if cls is not None:
                            MessageNew(lbl, cls, 10, sub, True)
                except Exception:
                    pass
            except Exception:
                pass
        except Exception:
            # Non-fatal: if creating wrappers fails, GUI can still initialize and show diagnostics
            pass
        return True

    def start(self) -> bool:
        """Start sender/receiver threads. Returns True if started."""
        if not self._imported:
            self.import_error = 'Library not initialized; call init_library() first.'
            return False

        # If already running, nothing to do
        if self._running:
            return True

        try:
            # Ensure the receive socket is bound before starting receiver
            init_recv = getattr(self._utils_mod, 'init_recv_sock', None)
            if callable(init_recv):
                ok = init_recv()
                if not ok:
                    self.import_error = getattr(self._utils_mod, 'get_bind_error', lambda: 'bind failed')()
                    return False

            canSend = getattr(self._utils_mod, 'canSend', None)
            STOP_ALL = getattr(self._utils_mod, 'STOP_ALL', None)

            # Ensure STOP_ALL is cleared so threads will run after a previous stop()
            try:
                if STOP_ALL is not None and hasattr(STOP_ALL, 'clear'):
                    STOP_ALL.clear()
            except Exception:
                pass

            # Start sender thread if MajorFrame has a send loop
            major = getattr(self._messages_mod, 'MajorFrame', None)
            send_target = None
            if major is not None and hasattr(major, 'send_frame_at_right_time_new'):
                send_target = major.send_frame_at_right_time_new
            else:
                # Fallback: MajorFrame may have a different send method
                send_target = getattr(major, 'send_frame_at_right_time', None)

            # Receiver function
            recv_target = getattr(self._messages_mod, 'recv_messages', None)

            if send_target is not None:
                # Many send loops expect (stop, canSend) positional args.
                sender_args = (STOP_ALL, canSend)
                self._sender_thread = Thread(target=send_target, args=sender_args, daemon=True)
                self._sender_thread.start()

            if recv_target is not None:
                self._recv_thread = Thread(target=recv_target, daemon=True)
                self._recv_thread.start()

            # enable sending flag if available
            if canSend is not None and hasattr(canSend, 'set'):
                canSend.set()

            self._running = True
            return True
        except Exception as e:
            self.import_error = str(e)
            return False

    def stop(self, timeout: float = 1.0):
        if not self._running:
            return
        try:
            STOP_ALL = getattr(self._utils_mod, 'STOP_ALL', None)
            canSend = getattr(self._utils_mod, 'canSend', None)
            if STOP_ALL is not None and hasattr(STOP_ALL, 'set'):
                STOP_ALL.set()
            if canSend is not None and hasattr(canSend, 'clear'):
                canSend.clear()
            # Close the receive socket so it can be rebound on restart
            try:
                close_sock = getattr(self._utils_mod, 'close_recv_sock', None)
                if callable(close_sock):
                    close_sock()
            except Exception:
                pass
        except Exception:
            pass
        time.sleep(timeout)
        self._running = False

    def is_running(self) -> bool:
        return self._running


# module-level lazy manager
_manager: Optional[ConnectionManager] = None


def get_manager() -> ConnectionManager:
    global _manager
    if _manager is None:
        _manager = ConnectionManager()
    return _manager
