"""BlueZ GATT adapter using the bless library.

This module bridges our GATT server handlers to BlueZ via bless,
enabling BLE peripheral functionality on Linux.

Architecture:
    bless BLEServer (handles BlueZ D-Bus)
           |
    BlueZGATTAdapter (this file - bridges handlers)
           |
    DangerousPiGATTServer (handlers call service layer)
           |
    Service Layer (business logic)
"""
import asyncio
import logging
import sys
import threading
from typing import Dict, Optional, Any, Union

from bless import (
    BlessServer,
    BlessGATTCharacteristic,
    GATTCharacteristicProperties,
    GATTAttributePermissions,
)

from .gatt_server import DangerousPiGATTServer
from .characteristics import (
    PM3CharacteristicUUIDs,
    WiFiCharacteristicUUIDs,
    SystemCharacteristicUUIDs,
    UpdateCharacteristicUUIDs,
)

logger = logging.getLogger(__name__)

# Device name for BLE advertising
DEFAULT_DEVICE_NAME = "Dangerous-Pi"


class BlueZGATTAdapter:
    """Adapter connecting bless BLE server to our GATT handlers.

    This class:
    - Initializes the bless BLE server
    - Registers all services and characteristics via GATT dictionary
    - Routes read/write requests to DangerousPiGATTServer handlers
    - Sends notifications via bless
    """

    def __init__(self, device_name: str = DEFAULT_DEVICE_NAME):
        """Initialize the BlueZ GATT adapter.

        Args:
            device_name: BLE device name for advertising
        """
        self.device_name = device_name
        self.server: Optional[BlessServer] = None
        self.gatt_server = DangerousPiGATTServer()
        self._is_running = False
        self._loop: Optional[asyncio.AbstractEventLoop] = None

        # Platform-specific trigger for async coordination
        self._trigger: Union[asyncio.Event, threading.Event]
        if sys.platform in ["darwin", "win32"]:
            self._trigger = threading.Event()
        else:
            self._trigger = asyncio.Event()

    @property
    def is_running(self) -> bool:
        """Check if BLE server is running."""
        return self._is_running

    def _build_gatt_dict(self) -> Dict:
        """Build the GATT dictionary for bless.

        Returns:
            Dictionary mapping service UUIDs to characteristic definitions
        """
        return {
            # PM3 Service
            PM3CharacteristicUUIDs.SERVICE: {
                PM3CharacteristicUUIDs.COMMAND_WRITE: {
                    "Properties": GATTCharacteristicProperties.write,
                    "Permissions": GATTAttributePermissions.writeable,
                    "Value": None,
                },
                PM3CharacteristicUUIDs.COMMAND_RESULT: {
                    "Properties": (
                        GATTCharacteristicProperties.read |
                        GATTCharacteristicProperties.notify
                    ),
                    "Permissions": GATTAttributePermissions.readable,
                    "Value": bytearray(b'{}'),
                },
                PM3CharacteristicUUIDs.STATUS: {
                    "Properties": GATTCharacteristicProperties.read,
                    "Permissions": GATTAttributePermissions.readable,
                    "Value": bytearray(b'{"connected": false}'),
                },
                PM3CharacteristicUUIDs.SESSION_CREATE: {
                    "Properties": GATTCharacteristicProperties.write,
                    "Permissions": GATTAttributePermissions.writeable,
                    "Value": None,
                },
                PM3CharacteristicUUIDs.SESSION_RELEASE: {
                    "Properties": GATTCharacteristicProperties.write,
                    "Permissions": GATTAttributePermissions.writeable,
                    "Value": None,
                },
            },
            # WiFi Service
            WiFiCharacteristicUUIDs.SERVICE: {
                WiFiCharacteristicUUIDs.STATUS: {
                    "Properties": GATTCharacteristicProperties.read,
                    "Permissions": GATTAttributePermissions.readable,
                    "Value": bytearray(b'{"mode": "unknown"}'),
                },
                WiFiCharacteristicUUIDs.SCAN: {
                    "Properties": (
                        GATTCharacteristicProperties.write |
                        GATTCharacteristicProperties.notify
                    ),
                    "Permissions": (
                        GATTAttributePermissions.readable |
                        GATTAttributePermissions.writeable
                    ),
                    "Value": None,
                },
                WiFiCharacteristicUUIDs.CONNECT: {
                    "Properties": GATTCharacteristicProperties.write,
                    "Permissions": GATTAttributePermissions.writeable,
                    "Value": None,
                },
                WiFiCharacteristicUUIDs.MODE: {
                    "Properties": (
                        GATTCharacteristicProperties.read |
                        GATTCharacteristicProperties.write
                    ),
                    "Permissions": (
                        GATTAttributePermissions.readable |
                        GATTAttributePermissions.writeable
                    ),
                    "Value": bytearray(b'client'),
                },
            },
            # System Service
            SystemCharacteristicUUIDs.SERVICE: {
                SystemCharacteristicUUIDs.INFO: {
                    "Properties": (
                        GATTCharacteristicProperties.read |
                        GATTCharacteristicProperties.notify
                    ),
                    "Permissions": GATTAttributePermissions.readable,
                    "Value": bytearray(b'{}'),
                },
                SystemCharacteristicUUIDs.SHUTDOWN: {
                    "Properties": GATTCharacteristicProperties.write,
                    "Permissions": GATTAttributePermissions.writeable,
                    "Value": None,
                },
                SystemCharacteristicUUIDs.RESTART: {
                    "Properties": GATTCharacteristicProperties.write,
                    "Permissions": GATTAttributePermissions.writeable,
                    "Value": None,
                },
            },
            # Update Service
            UpdateCharacteristicUUIDs.SERVICE: {
                UpdateCharacteristicUUIDs.CHECK: {
                    "Properties": (
                        GATTCharacteristicProperties.write |
                        GATTCharacteristicProperties.notify
                    ),
                    "Permissions": (
                        GATTAttributePermissions.readable |
                        GATTAttributePermissions.writeable
                    ),
                    "Value": None,
                },
                UpdateCharacteristicUUIDs.PROGRESS: {
                    "Properties": (
                        GATTCharacteristicProperties.read |
                        GATTCharacteristicProperties.notify
                    ),
                    "Permissions": GATTAttributePermissions.readable,
                    "Value": bytearray(b'{"progress": 0}'),
                },
            },
        }

    def _on_read(self, characteristic: BlessGATTCharacteristic) -> bytearray:
        """Handle BLE read request.

        Note: This callback is called synchronously from the D-Bus event handler.
        We cannot block on async operations here as it would deadlock the event loop.
        Instead, we return cached values and schedule async updates in the background.

        Args:
            characteristic: The characteristic being read

        Returns:
            Characteristic value as bytearray
        """
        uuid = str(characteristic.uuid).lower()
        logger.info("BLE Read request for characteristic %s", uuid)

        # Return the current cached value (synchronously)
        # Async handlers update these values in the background
        if characteristic.value:
            logger.info("Read returning cached: %s", characteristic.value[:50] if len(characteristic.value) > 50 else characteristic.value)
            return characteristic.value

        # Return default JSON if no cached value
        default_value = bytearray(b'{"status": "initializing"}')
        logger.info("Read returning default: %s", default_value)
        return default_value

    def _on_write(self, characteristic: BlessGATTCharacteristic, value: Any):
        """Handle BLE write request.

        Args:
            characteristic: The characteristic being written
            value: Value being written
        """
        uuid = str(characteristic.uuid).lower()  # Use lowercase to match our UUID format
        logger.info("BLE Write request for characteristic %s: %s", uuid, value)

        # Update the characteristic value
        characteristic.value = value

        handler = self.gatt_server.get_characteristic_handler(uuid)
        if handler and handler.write_handler:
            try:
                # Convert value to bytes if needed
                if isinstance(value, bytearray):
                    data = bytes(value)
                elif isinstance(value, bytes):
                    data = value
                else:
                    data = bytes(value)

                # Run async handler in event loop
                if self._loop and self._loop.is_running():
                    asyncio.run_coroutine_threadsafe(
                        handler.write_handler(data),
                        self._loop
                    )
            except Exception as e:
                logger.error("Error handling write for %s: %s", uuid, e)

    def _on_subscribe(self, characteristic: BlessGATTCharacteristic, **kwargs):
        """Handle subscription to characteristic notifications."""
        logger.info("Client subscribed to %s", characteristic.uuid)

    def _on_unsubscribe(self, characteristic: BlessGATTCharacteristic, **kwargs):
        """Handle unsubscription from characteristic notifications."""
        logger.info("Client unsubscribed from %s", characteristic.uuid)

    async def start(self) -> bool:
        """Start the BLE GATT server.

        Returns:
            True if started successfully, False otherwise
        """
        if self._is_running:
            logger.warning("BLE server already running")
            return True

        try:
            self._loop = asyncio.get_running_loop()

            # Create bless server
            self.server = BlessServer(name=self.device_name, loop=self._loop)

            # Set up callbacks (bless 0.3.0+ API)
            self.server.read_request_func = self._on_read
            self.server.write_request_func = self._on_write

            # Build and add GATT structure
            gatt = self._build_gatt_dict()
            await self.server.add_gatt(gatt)

            # Start the server (begins advertising)
            await self.server.start()

            # Start GATT server handlers
            await self.gatt_server.start()

            # Register notification callbacks
            self._setup_notification_callbacks()

            self._is_running = True
            logger.info("BLE GATT server started, advertising as '%s'", self.device_name)
            return True

        except Exception as e:
            logger.error("Failed to start BLE server: %s", e)
            import traceback
            traceback.print_exc()
            self._is_running = False
            return False

    def _setup_notification_callbacks(self):
        """Set up notification callbacks for characteristics that support notify."""
        notify_chars = [
            PM3CharacteristicUUIDs.COMMAND_RESULT,
            WiFiCharacteristicUUIDs.SCAN,
            SystemCharacteristicUUIDs.INFO,
            UpdateCharacteristicUUIDs.CHECK,
            UpdateCharacteristicUUIDs.PROGRESS,
        ]

        for uuid in notify_chars:
            self._register_notification_callback(uuid)

    def _register_notification_callback(self, uuid: str):
        """Register notification callback for a characteristic.

        Args:
            uuid: Characteristic UUID
        """
        async def send_notification(value: bytes):
            if self.server and self._is_running:
                try:
                    char = self.server.get_characteristic(uuid)
                    if char:
                        char.value = bytearray(value)
                        service_uuid = self._get_service_uuid_for_characteristic(uuid)
                        # update_value is not async in bless 0.3+
                        self.server.update_value(service_uuid, uuid)
                        logger.debug("Notification sent for %s", uuid)
                except Exception as e:
                    logger.error("Failed to send notification for %s: %s", uuid, e)

        self.gatt_server.register_notification_callback(uuid, send_notification)

    def _get_service_uuid_for_characteristic(self, char_uuid: str) -> str:
        """Get the service UUID that contains a characteristic.

        Args:
            char_uuid: Characteristic UUID

        Returns:
            Service UUID
        """
        # Check each service's characteristics
        if char_uuid in [
            PM3CharacteristicUUIDs.COMMAND_WRITE,
            PM3CharacteristicUUIDs.COMMAND_RESULT,
            PM3CharacteristicUUIDs.STATUS,
            PM3CharacteristicUUIDs.SESSION_CREATE,
            PM3CharacteristicUUIDs.SESSION_RELEASE,
        ]:
            return PM3CharacteristicUUIDs.SERVICE
        elif char_uuid in [
            WiFiCharacteristicUUIDs.STATUS,
            WiFiCharacteristicUUIDs.SCAN,
            WiFiCharacteristicUUIDs.CONNECT,
            WiFiCharacteristicUUIDs.MODE,
        ]:
            return WiFiCharacteristicUUIDs.SERVICE
        elif char_uuid in [
            SystemCharacteristicUUIDs.INFO,
            SystemCharacteristicUUIDs.SHUTDOWN,
            SystemCharacteristicUUIDs.RESTART,
        ]:
            return SystemCharacteristicUUIDs.SERVICE
        elif char_uuid in [
            UpdateCharacteristicUUIDs.CHECK,
            UpdateCharacteristicUUIDs.PROGRESS,
        ]:
            return UpdateCharacteristicUUIDs.SERVICE
        else:
            return PM3CharacteristicUUIDs.SERVICE  # Default

    async def stop(self):
        """Stop the BLE GATT server."""
        if not self._is_running:
            return

        try:
            await self.gatt_server.stop()

            if self.server:
                await self.server.stop()
                self.server = None

            self._is_running = False
            logger.info("BLE server stopped")

        except Exception as e:
            logger.error("Error stopping BLE server: %s", e)

    async def send_notification(self, uuid: str, value: bytes) -> bool:
        """Send a notification for a characteristic.

        Args:
            uuid: Characteristic UUID
            value: Notification value

        Returns:
            True if sent successfully
        """
        if not self.server or not self._is_running:
            return False

        try:
            char = self.server.get_characteristic(uuid)
            if char:
                char.value = bytearray(value)
                service_uuid = self._get_service_uuid_for_characteristic(uuid)
                # update_value is not async in bless 0.3+
                self.server.update_value(service_uuid, uuid)
                return True
        except Exception as e:
            logger.error("Error sending notification for %s: %s", uuid, e)

        return False


# Singleton instance for application use
_adapter_instance: Optional[BlueZGATTAdapter] = None


def get_ble_adapter() -> BlueZGATTAdapter:
    """Get or create the singleton BLE adapter instance.

    Returns:
        BlueZGATTAdapter instance
    """
    global _adapter_instance
    if _adapter_instance is None:
        _adapter_instance = BlueZGATTAdapter()
    return _adapter_instance


async def start_ble_server(device_name: str = DEFAULT_DEVICE_NAME) -> bool:
    """Start the BLE GATT server.

    Args:
        device_name: BLE device name for advertising

    Returns:
        True if started successfully
    """
    adapter = get_ble_adapter()
    adapter.device_name = device_name
    return await adapter.start()


async def stop_ble_server():
    """Stop the BLE GATT server."""
    adapter = get_ble_adapter()
    await adapter.stop()
