import tkinter as tk
from tkinter import ttk
from typing import Callable, Optional, Dict, Any


class ConnectionPanel(ttk.LabelFrame):
    """Reusable connection panel used by MainView.

    This encapsulates the small UI that shows connection type/summary and
    exposes simple hooks so the outer view can attach handlers for Connect
    and Settings.
    """

    def __init__(self, parent, initial_config: Optional[Dict[str, Any]] = None):
        super().__init__(parent, text="Connection")

        self._on_connect: Optional[Callable[[], None]] = None
        self._on_open_settings: Optional[Callable[[], None]] = None

        self.conn_type_var = tk.StringVar(
            value=(initial_config or {}).get("target", {}).get("type", "-")
        )
        self.conn_info_var = tk.StringVar(
            value=self._format_summary((initial_config or {}).get("target", {}))
        )

        ttk.Label(self, text="Type:").pack(side=tk.LEFT, padx=(6, 2))
        ttk.Label(self, textvariable=self.conn_type_var, width=10).pack(side=tk.LEFT)
        ttk.Label(self, textvariable=self.conn_info_var).pack(side=tk.LEFT, padx=(8, 4))

        # Buttons
        self.connect_button = ttk.Button(
            self, text="Connect", command=self._handle_connect
        )
        self.connect_button.pack(side=tk.RIGHT, padx=(4, 6))

        self.conn_settings_button = ttk.Button(
            self, text="Settings...", command=self._handle_open_settings
        )
        self.conn_settings_button.pack(side=tk.RIGHT, padx=(4, 0))

    def _format_summary(self, cfg: Dict[str, Any]) -> str:
        """Return a compact human-readable summary for the connection config.

        Args:
            cfg (Dict[str, Any]): The connection configuration for a single
                communicator (e.g., sfp/serial/tftp sections).

        Returns:
            str: A short summary suitable for display in the UI.
        """
        try:
            t = cfg.get("type")
            if not t:
                return "-"
            if t == "sfp":
                sfp = cfg.get("sfp", {})
                ip = sfp.get("ip") or sfp.get("host") or "?"
                remote = sfp.get("port") or sfp.get("remote_port")
                if isinstance(remote, (list, tuple)):
                    remote_str = ",".join(str(int(p)) for p in remote)
                else:
                    try:
                        remote_str = str(int(remote)) if remote is not None else "?"
                    except Exception:
                        remote_str = str(remote)
                local = sfp.get("local_port")
                if local is not None:
                    try:
                        local_str = str(int(local))
                    except Exception:
                        local_str = str(local)
                    return f"{ip} (remote:{remote_str} local:{local_str})"
                return f"{ip} (remote:{remote_str})"
            if t == "serial":
                s = cfg.get("serial", {})
                port = s.get("port") or s.get("device") or "?"
                baud = s.get("baudrate") or s.get("baud") or "?"
                return f"{port} @{baud}"
            if t == "tftp":
                tftp = cfg.get("tftp", {})
                host = tftp.get("host") or tftp.get("server") or "?"
                return f"{host}"
            return "-"
        except Exception:
            return "-"

    def set_connect_handler(self, cb: Callable[[], None]):
        """Register a callback to be invoked when the Connect button is pressed.

        The callback receives no arguments.
        """
        self._on_connect = cb

    def set_open_settings_handler(self, cb: Callable[[], None]):
        """Register a callback to open connection settings UI.

        The callback receives no arguments and is expected to display the
        settings window or dialog when invoked.
        """
        self._on_open_settings = cb

    def _handle_connect(self):
        """Invoke the registered connect handler (if any).

        This method delegates to the user-provided handler and ensures that
        handler exceptions propagate so the caller can manage them.
        """
        if self._on_connect:
            try:
                self._on_connect()
            except Exception:
                # Handler should manage its own errors
                raise

    def _handle_open_settings(self):
        """Invoke the registered 'open settings' handler (if any)."""
        if self._on_open_settings:
            try:
                self._on_open_settings()
            except Exception:
                raise

    def update_summary(self, conn_config: Dict[str, Any]):
        """Update the displayed connection summary according to configuration.

        Args:
            conn_config (Dict[str, Any]): Full configuration dictionary where
                the 'target' key holds the per-communicator section.
        """
        try:
            self.conn_type_var.set(
                (conn_config or {}).get("target", {}).get("type", "-")
            )
            self.conn_info_var.set(
                self._format_summary((conn_config or {}).get("target", {}))
            )
        except Exception:
            pass

    def update_toggle_state(self, is_connected: bool):
        """Change the Connect button label to reflect connection state.

        Args:
            is_connected (bool): If True, button shows 'Disconnect'. Otherwise
                it shows 'Connect'.
        """
        try:
            self.connect_button.config(text="Disconnect" if is_connected else "Connect")
        except Exception:
            pass
