from PyQt6.QtWidgets import (
    QWidget,
    QVBoxLayout,
    QHBoxLayout,
    QPushButton,
    QComboBox,
    QLineEdit,
    QSplitter,
)
from PyQt6.QtCore import Qt
from PyQt6.QtCore import QObject, pyqtSignal

from components.console.console_ui import console_widget
from components.console.console_registry import log_main_console
import components.items.elements as elements
from components.commands.command_table_ui import command_table_widget
import config.config as config

from components.i2c.i2c_logic import I2CLogic, _coerce_int
from components.i2c.i2c_command_editor import I2CCommandEditorDialog
from components.data import db


class _SafeConsoleProxy(QObject):
    log_signal = pyqtSignal(str, str)

    def __init__(self, console):
        super().__init__()
        self.log_signal.connect(console.log)

    def __call__(self, level, msg):
        # called from worker thread → emits safely into UI thread
        self.log_signal.emit(level, msg)


class I2CHandler:
    """Adapter used by command_table_widget → forwards to the I2CWidget."""

    def __init__(self, widget: "I2CWidget"):
        self.w = widget

    def get_command_list(self):
        return self.w.i2c_logic.get_predefined_commands()

    def send_command(self, command: dict):
        self.w.send_command(command)

    def add_command(self):
        dialog = I2CCommandEditorDialog()
        if dialog.exec():
            command = dialog.get_data()
            db.add_i2c_command(command)

    def modify_command(self, command):
        dialog = I2CCommandEditorDialog(command=command)
        if dialog.exec():
            updated = dialog.get_data()
            updated["id"] = command["id"]
            db.modify_i2c_command(updated)

    def delete_command(self, command):
        db.delete_i2c_command(command)


class I2CWidget(QWidget):
    """Drop-in single-page I2C widget with table | console splitter."""

    def __init__(self, parent=None):
        super().__init__(parent)
        self.i2c_logic = I2CLogic()
        self.handler = I2CHandler(self)
        self.commands = self.i2c_logic.get_predefined_commands()
        self.comboboxes = {}
        self.connection_status = False
        self.init_ui()

    def init_ui(self):
        # Top controls
        top_controls = QWidget()
        top_controls_layout = QHBoxLayout(top_controls)
        top_controls_layout.setContentsMargins(0, 0, 0, 0)
        top_controls_layout.setSpacing(12)
        top_controls_layout.setAlignment(Qt.AlignmentFlag.AlignTop)

        # Port
        self.comboboxes["port"] = QComboBox()
        self.comboboxes["port"].addItems(self.i2c_logic.get_channels())
        top_controls_layout.addWidget(
            elements.label_and_widget("Port", self.comboboxes["port"])
        )

        self.button_refresh_channels = elements.create_icon_button(
            config.REFRESH_BUTTON_ICON_LINK, icon_size=30, border_size=4
        )
        top_controls_layout.addWidget(self.button_refresh_channels)

        # Address (empty until connected → scan)
        self.comboboxes["address"] = QComboBox()
        top_controls_layout.addWidget(
            elements.label_and_widget("Address", self.comboboxes["address"])
        )

        self.button_refresh_address = elements.create_icon_button(
            config.REFRESH_BUTTON_ICON_LINK, icon_size=30, border_size=4
        )
        top_controls_layout.addWidget(self.button_refresh_address)

        # Connect / Disconnect
        self.button_connect = QPushButton("Connect")
        top_controls_layout.addWidget(
            elements.label_and_widget("", self.button_connect)
        )

        self.button_disconnect = QPushButton("Disconnect")
        top_controls_layout.addWidget(
            elements.label_and_widget("", self.button_disconnect)
        )

        # Command table
        self.command_table = command_table_widget(
            commands=self.commands, handler=self.handler
        )

        col1_widget = QWidget()
        col1_layout = QVBoxLayout(col1_widget)
        col1_layout.setContentsMargins(0, 0, 0, 0)
        col1_layout.setSpacing(4)
        col1_layout.addWidget(self.command_table)

        # Input + Send
        input_line_layout = QHBoxLayout()
        input_line_layout.setContentsMargins(0, 0, 0, 0)
        input_line_layout.setSpacing(4)

        self.comboboxes["action"] = QComboBox()
        self.comboboxes["action"].addItems(["Read", "Write"])
        input_line_layout.addWidget(
            elements.label_and_widget("Action", self.comboboxes["action"])
        )
        self.input_register = QLineEdit()
        self.input_register.setPlaceholderText("0x04")
        input_line_layout.addWidget(
            elements.label_and_widget("Register", self.input_register)
        )

        self.input_hex = QLineEdit()
        self.input_hex.setPlaceholderText(
            "Hex bytes (e.g. 0x01,0x02,0x03) → block write to reg 0x00"
        )
        input_line_layout.addWidget(elements.label_and_widget("Data", self.input_hex))
        self.input_length = QLineEdit()

        self.input_length.setPlaceholderText("1")
        input_line_layout.addWidget(
            elements.label_and_widget("Length", self.input_length)
        )

        self.button_send_raw = QPushButton("Send Raw")
        input_line_layout.addWidget(elements.label_and_widget("", self.button_send_raw))

        # Console
        self.console = console_widget()
        self.i2c_logic.set_logger(self.console.log)

        console_stack_widget = QWidget()
        console_stack_layout = QVBoxLayout(console_stack_widget)
        console_stack_layout.setContentsMargins(0, 0, 0, 0)
        console_stack_layout.setSpacing(4)
        console_stack_layout.addLayout(input_line_layout)
        console_stack_layout.addWidget(self.console)

        # Splitter: table | console
        splitter = QSplitter(Qt.Orientation.Horizontal)
        splitter.addWidget(col1_widget)
        splitter.addWidget(console_stack_widget)
        splitter.setSizes([740, 1200])
        splitter.setStretchFactor(0, 0)
        splitter.setStretchFactor(1, 1)

        # Main layout
        main_layout = QVBoxLayout()
        main_layout.setContentsMargins(4, 4, 4, 4)
        main_layout.setSpacing(6)
        main_layout.addWidget(top_controls)
        main_layout.addWidget(splitter, stretch=1)
        self.setLayout(main_layout)

        # Signals
        self.button_refresh_channels.clicked.connect(self.refresh_channels)
        self.button_refresh_address.clicked.connect(self.refresh_address)
        self.button_connect.clicked.connect(self.connect)
        self.button_disconnect.clicked.connect(self.disconnect)
        self.button_send_raw.clicked.connect(self.send_command_raw)

        self.disconnected_enable_status()

    # --- UI state toggles ---
    def disconnected_enable_status(self):
        elements.set_enabled_state(True, self.button_refresh_channels, grayOut=False)
        elements.set_enabled_state(True, self.comboboxes["port"], grayOut=False)
        elements.set_enabled_state(True, self.command_table, grayOut=False)
        elements.set_enabled_state(False, self.input_hex, grayOut=True)
        elements.set_enabled_state(False, self.button_send_raw, grayOut=True)
        elements.set_enabled_state(False, self.button_disconnect, grayOut=True)
        elements.set_enabled_state(True, self.button_connect, grayOut=False)
        elements.set_enabled_state(False, self.button_refresh_address, grayOut=True)
        elements.set_enabled_state(False, self.comboboxes["address"], grayOut=True)
        elements.set_enabled_state(False, self.comboboxes["action"], grayOut=True)
        elements.set_enabled_state(False, self.input_register, grayOut=True)

    def connected_enable_status(self):
        elements.set_enabled_state(False, self.button_refresh_channels, grayOut=True)
        elements.set_enabled_state(False, self.comboboxes["port"], grayOut=True)
        elements.set_enabled_state(True, self.command_table, grayOut=False)
        elements.set_enabled_state(True, self.input_hex, grayOut=False)
        elements.set_enabled_state(True, self.button_send_raw, grayOut=False)
        elements.set_enabled_state(True, self.button_disconnect, grayOut=False)
        elements.set_enabled_state(False, self.button_connect, grayOut=True)
        elements.set_enabled_state(True, self.button_refresh_address, grayOut=False)
        elements.set_enabled_state(True, self.comboboxes["address"], grayOut=False)
        elements.set_enabled_state(True, self.comboboxes["action"], grayOut=False)
        elements.set_enabled_state(True, self.input_register, grayOut=False)

    # --- Buttons ---
    def connect(self):
        log_main_console("info", "🔗 Connecting...")
        success = self.i2c_logic.connect(self.comboboxes["port"].currentText())
        if success:
            self.connected_enable_status()
            self.command_table.set_connected_state()
            self.refresh_address(silent=True)
            self.connection_status = True
        else:
            elements.flash_button(
                self.button_connect, flash_style="background-color: red;"
            )

    def disconnect(self):
        log_main_console("info", "🔌 Disconnecting...")
        if self.i2c_logic.stop_logger():
            self.disconnected_enable_status()
            self.command_table.set_disconnected_state()
            self.connection_status = False
            self.refresh_channels(silent=True)
        else:
            elements.flash_button(
                self.button_disconnect, flash_style="background-color: red;"
            )

    def refresh_channels(self, silent: bool = False):
        log_main_console("info", "🔄 Refreshing buses...")
        self.comboboxes["port"].clear()
        ports = self.i2c_logic.get_channels()
        if ports:
            self.comboboxes["port"].addItems(ports)
            if not silent:
                elements.flash_button(self.button_refresh_channels)
            log_main_console("success", "🔄 Bus list refreshed")
        else:
            elements.flash_button(
                self.button_refresh_channels, flash_style="background-color: red;"
            )
            log_main_console("warn", "No I2C buses found")

    def refresh_address(self, silent: bool = False):
        log_main_console("info", "🔄 Scanning bus for devices...")
        self.comboboxes["address"].clear()
        addresses = self.i2c_logic.scan_bus()
        if addresses:
            self.comboboxes["address"].addItems(addresses)
            if not silent:
                elements.flash_button(self.button_refresh_address)
            log_main_console("success", "🔄 Bus scan complete")
        else:
            elements.flash_button(
                self.button_refresh_address, flash_style="background-color: red;"
            )
            log_main_console("info", "No devices detected")

    def get_current_config(self):
        return {key: cb.currentText() for key, cb in self.comboboxes.items()}

    def _current_address_int(self) -> int:
        txt = self.comboboxes["address"].currentText().strip() or "0x40"
        return _coerce_int(txt)

    def send_command(self, command: dict):
        self.i2c_logic.send_command(command, device_address=self._current_address_int())

    def send_command_raw(self):
        action = self.comboboxes["action"].currentText().strip().upper()
        reg_txt = (self.input_register.text() or "").strip()
        data_txt = (self.input_hex.text() or "").strip()
        length_txt = (self.input_length.text() or "").strip()

        # --- helpers ---
        def _to_int_any(s, default=None):
            try:
                return int(str(s), 0)  # accepts "0xFE", "254"
            except Exception:
                return default

        def _split_tokens(s: str):
            return [t for t in s.replace(",", " ").split() if t]

        # --- validate register ---
        reg = _to_int_any(reg_txt)
        if reg is None:
            self.console.log("warning", "⚠️ Invalid or missing register (e.g. 0xFE).")
            return

        cmd = {
            "action": action.lower(),
            "reg": reg,
        }

        if action == "READ":
            ln = _to_int_any(length_txt, default=1)
            if ln is None or ln <= 0:
                self.console.log("warning", "⚠️ Invalid length (must be >0).")
                return
            cmd["length"] = ln

        elif action == "WRITE":
            toks = _split_tokens(data_txt)
            if not toks:
                self.console.log(
                    "warning", "⚠️ WRITE requires data (e.g. 00 or 01,02,03)."
                )
                return
            if len(toks) == 1:
                v = _to_int_any(toks[0])
                if v is None:
                    self.console.log(
                        "warning", "⚠️ Invalid byte. Use hex/dec like 0x00 or 0."
                    )
                    return
                cmd["value"] = v
            else:
                cmd["bytes"] = data_txt  # leave as string; logic will parse

        self.i2c_logic.send_command(cmd, device_address=self._current_address_int())
