import asyncio
import threading
from bleak import BleakClient, BleakScanner

UART_RX_CHAR_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
UART_TX_CHAR_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"


class TelemetrixBleSync:
    """
    Synchronous wrapper for BLE UART communication.
    No async/await required for users!
    """

    def __init__(self, ble_device_name, receive_callback=None):
        self.ble_device_name = ble_device_name
        self.receive_callback = receive_callback
        self.loop = None
        self.thread = None
        self.client = None
        self.connected = False

    def connect(self, timeout=10):
        """Connect to BLE device (synchronous call)"""
        # Start background thread with event loop
        self.thread = threading.Thread(target=self._run_event_loop, daemon=True)
        self.thread.start()

        # Wait for loop to be ready
        import time
        while self.loop is None:
            time.sleep(0.01)

        # Run connection in background loop
        future = asyncio.run_coroutine_threadsafe(
            self._connect_async(), self.loop
        )

        try:
            result = future.result(timeout=timeout)
            self.connected = result
            return result
        except Exception as e:
            print(f"Connection failed: {e}")
            return False

    def write(self, data, timeout=5):
        """Write data (synchronous call)"""
        if not self.connected:
            print("Not connected")
            return False

        if isinstance(data, list):
            data = bytes(data)

        future = asyncio.run_coroutine_threadsafe(
            self._write_async(data), self.loop
        )

        try:
            future.result(timeout=timeout)
            return True
        except Exception as e:
            print(f"Write failed: {e}")
            return False

    def disconnect(self):
        """Disconnect (synchronous call)"""
        if not self.connected:
            return

        future = asyncio.run_coroutine_threadsafe(
            self._disconnect_async(), self.loop
        )

        try:
            future.result(timeout=5)
            self.connected = False
        except Exception as e:
            print(f"Disconnect failed: {e}")

    def _run_event_loop(self):
        """Background thread running asyncio"""
        self.loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self.loop)
        self.loop.run_forever()

    async def _connect_async(self):
        """Internal async connection"""
        print(f'Scanning for {self.ble_device_name}...')

        device = await BleakScanner.find_device_by_name(self.ble_device_name)
        if device is None:
            print(f'Device not found')
            return False

        print(f'Found {self.ble_device_name} at {device.address}')

        self.client = BleakClient(device.address)
        await self.client.connect()

        if not self.client.is_connected:
            return False

        if self.receive_callback:
            await self.client.start_notify(
                UART_TX_CHAR_UUID,
                self._notification_handler
            )

        print('Connected!')
        return True

    async def _write_async(self, data):
        """Internal async write"""
        await self.client.write_gatt_char(UART_RX_CHAR_UUID, data)

    async def _disconnect_async(self):
        """Internal async disconnect"""
        if self.client and self.client.is_connected:
            await self.client.disconnect()

    def _notification_handler(self, sender, data):
        """Handle incoming data"""
        if self.receive_callback:
            self.receive_callback(data)


# Test it
if __name__ == "__main__":
    import time


    def on_data(data):
        print(f"Received: {list(data)}")


    ble = TelemetrixBleSync("Tmx4Pico2W", receive_callback=on_data)

    if ble.connect():
        print("\nSending [1, 62]...")
        ble.write([1, 62])

        time.sleep(2)  # Wait for