import asyncio
import logging
import re

from typing import Callable

from .ble_protocol import BLEProtocol
from .on_receive import OnReceive
from .rfid import RfidCommands
from .serial_protocol import SerialProtocol
from .tcp_protocol import TCPProtocol
from .write_commands import WriteCommands

from smartx_rfid.utils.event import on_event
from smartx_rfid.devices._base import DeviceBase

ant_default_config = {
    "1": {"active": True, "power": 22, "rssi": -120},
    "2": {"active": False, "power": 22, "rssi": -120},
    "3": {"active": False, "power": 22, "rssi": -120},
    "4": {"active": False, "power": 22, "rssi": -120},
}


class X714(DeviceBase, SerialProtocol, OnReceive, RfidCommands, BLEProtocol, WriteCommands, TCPProtocol):
    """RFID reader that supports SERIAL, TCP and BLE connections."""

    def __init__(
        self,
        name: str = "X714",
        connection_type: str = "SERIAL",  # SERIAL, BLE or TCP
        # SERIAL
        port: str = "AUTO",  # AUTO or COMx / /dev/ttyX
        baudrate: int = 115200,
        vid: int = 1,
        pid: int = 1,
        # TCP
        ip: str | None = "192.168.1.100",
        tcp_port: int = 23,
        # BLE
        ble_name: str = "SMTX",
        # GENERIC
        buzzer: bool = False,
        session: int = 1,  # 0, 1, 2, 3
        start_reading: bool = False,
        gpi_start: bool = False,
        always_send: bool = True,
        simple_send: bool = False,
        keyboard: bool = False,
        decode_gtin: bool = False,
        hotspot: bool = True,
        reconnection_time: int = 3,
        prefix: str = "",
        protected_inventory_active: bool = False,
        protected_inventory_password: str | None = "12345678",
        # Antenna config
        # If ant_dict is provided use it else use the other vars
        ant_dict: dict | None = None,
        active_ant: list[int] | None = [1],
        read_power: int = 22,
        read_rssi: int = -120,
        **kwargs,
    ):
        """
        Create X714 RFID reader.

        Args:
            name: Device name
            connection_type: How to connect (SERIAL, TCP, BLE)
            port: Serial port or AUTO to detect
            baudrate: Serial speed
            vid: USB vendor ID for auto-detect
            pid: USB product ID for auto-detect
            ip: IP address for TCP connection
            tcp_port: TCP port number
            ble_name: Bluetooth device name
            buzzer: Make sound when reading tags
            session: EPC session number (0-3)
            start_reading: Start reading tags automatically
            gpi_start: Use GPI input to start reading
            always_send: Send all tag events
            simple_send: Use simple tag format
            keyboard: Act like keyboard input
            decode_gtin: Decode GTIN barcodes
            hotspot: Enable hotspot mode
            reconnection_time: Seconds to wait before reconnect
            prefix: Text to add before tag data
            protected_inventory_active: Enable protected reading
            protected_inventory_password: Password for protected reading
            ant_dict: Custom antenna settings
            active_ant: Which antennas to use
            read_power: TX power in dBm
            read_rssi: RSSI threshold in dBm
        """
        DeviceBase.__init__(self)

        # Name
        self.name = name
        self.device_type = "rfid"

        # CONNECTION TYPE
        if connection_type in ["SERIAL", "BLE", "TCP"]:
            self.connection_type = connection_type
        else:
            self.connection_type = "SERIAL"
            logging.warning(f"[{self.name}] Invalid connection_type '{connection_type}' set to 'SERIAL'")

        # SERIAL CONFIG
        if not isinstance(port, str):
            port = "AUTO"
        self.port = port
        if not isinstance(baudrate, int):
            baudrate = 115200
        self.baudrate = baudrate
        if not isinstance(vid, int):
            vid = 1
        if not isinstance(pid, int):
            pid = 1
        self.vid = vid
        self.pid = pid
        self.is_auto = self.port == "AUTO"

        # TCP CONFIG
        if not isinstance(ip, str):
            ip = "192.168.1.100"
        if not isinstance(tcp_port, int):
            tcp_port = 23
        self.ip = ip
        self.tcp_port = tcp_port

        # BLE CONFIG
        if not isinstance(ble_name, str):
            ble_name = "SMTX"
        self.ble_name = ble_name
        self.init_ble_vars()

        # GENERIC CONFIG
        if not isinstance(buzzer, bool):
            buzzer = False
        self.buzzer = buzzer
        if session not in [0, 1, 2, 3]:
            session = 1
            logging.warning(f"[{self.name}] Invalid session '{session}' set to '1'")
        self.session = session

        # Modes
        if not isinstance(start_reading, bool):
            start_reading = False
        if not isinstance(gpi_start, bool):
            gpi_start = False
        if not isinstance(always_send, bool):
            always_send = True
        if not isinstance(simple_send, bool):
            simple_send = False
        if not isinstance(keyboard, bool):
            keyboard = False
        if not isinstance(decode_gtin, bool):
            decode_gtin = False
        if not isinstance(hotspot, bool):
            hotspot = True
        if not isinstance(reconnection_time, int):
            reconnection_time = 5
        self.start_reading = start_reading
        self.gpi_start = gpi_start
        self.always_send = always_send
        self.simple_send = simple_send
        self.keyboard = keyboard
        self.decode_gtin = decode_gtin
        self.hotspot = hotspot
        self.reconnection_time = reconnection_time

        # validate prefix
        if not isinstance(prefix, str):
            prefix = ""
        # validate hex
        if not re.match(r"^[0-9A-Fa-f]*$", prefix):
            prefix = ""

        self.prefix = prefix

        # PROTECTED
        if not isinstance(protected_inventory_active, bool):
            protected_inventory_active = False
        if not isinstance(protected_inventory_password, str):
            protected_inventory_password = ""
        self.protected_inventory_active = protected_inventory_active
        self.protected_inventory_password = protected_inventory_password

        # ANTENNA CONFIG
        if ant_dict is not None:
            self.ant_dict = ant_dict
        else:
            self.ant_dict = ant_default_config
            for ant in self.ant_dict.keys():
                ant_num = int(ant)
                if active_ant and ant_num in active_ant:
                    self.ant_dict[ant]["active"] = True
                else:
                    self.ant_dict[ant]["active"] = False
                self.ant_dict[ant]["power"] = read_power
                self.ant_dict[ant]["rssi"] = read_rssi

        self.serial_number = None

        self.transport = None
        self.on_con_lost = None
        self.rx_buffer = bytearray()
        self.last_byte_time = None

        self.is_connected = False
        self.is_reading = False
        self.is_gpi_trigger_on = self.gpi_start
        if self.is_gpi_trigger_on:
            self.start_reading = False

        self.on_event: Callable = on_event

    def write(self, to_send, verbose=True):
        """Send data to reader using current connection type.

        Args:
            to_send: Data to send
            verbose: Show sent data in logs
        """
        if self.connection_type == "SERIAL":
            self.write_serial(to_send, verbose)
        elif self.connection_type == "BLE":
            if hasattr(self, "create_task"):
                self.create_task(self.write_ble(to_send.encode(), verbose))
            else:
                asyncio.create_task(self.write_ble(to_send.encode(), verbose))
        else:
            if hasattr(self, "create_task"):
                self.create_task(self.write_tcp(to_send, verbose))
            else:
                asyncio.create_task(self.write_tcp(to_send, verbose))

    async def connect(self):
        """Connect to reader using configured connection type."""
        if self.connection_type == "SERIAL":
            await self.connect_serial()
        elif self.connection_type == "BLE":
            await self.connect_ble()
        else:
            await self.connect_tcp(self.ip, self.tcp_port)

    def on_connected(self):
        """Called when connection is established. Sets up reader."""
        self.config_reader()
        self.on_event(self.name, "connected", True)

    async def close(self):
        """Close connections and cancel background tasks for X714."""
        self._running = False

        # try to close underlying transport/writer depending on connection type
        try:
            if self.connection_type == "SERIAL":
                if getattr(self, "transport", None):
                    try:
                        self.transport.close()
                    except Exception:
                        pass
                    # signal on_con_lost if present
                    try:
                        if self.on_con_lost and not self.on_con_lost.is_set():
                            self.on_con_lost.set()
                    except Exception:
                        pass
            elif self.connection_type == "TCP":
                if getattr(self, "writer", None):
                    try:
                        self.writer.close()
                        await self.writer.wait_closed()
                    except Exception:
                        pass
                    self.writer = None
                    self.reader = None
            elif self.connection_type == "BLE":
                # BLE specific cleanup if available
                try:
                    if hasattr(self, "close_ble"):
                        await self.close_ble()
                except Exception:
                    pass
        except Exception:
            pass

        # final cleanup of device tasks
        await self.shutdown()
