// Copyright (c) 2025-2026 (r)evolve - Revolve Team LLC
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Objective-C delegate implementations for CoreBluetooth
//!
//! CoreBluetooth uses the delegate pattern for callbacks. This module defines
//! Rust structs that implement the required Objective-C protocols and forward
//! events to Rust async channels.
//!
//! ## Delegate Protocols
//!
//! - `CBCentralManagerDelegate`: Receives central manager state and discovery events
//! - `CBPeripheralDelegate`: Receives GATT client events (reads, writes, notifications)
//! - `CBPeripheralManagerDelegate`: Receives GATT server events

use std::collections::HashMap;
use std::sync::Mutex;

use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_core_bluetooth::{
    CBATTError, CBATTRequest, CBAdvertisementDataServiceUUIDsKey, CBCentralManager,
    CBCentralManagerDelegate, CBCharacteristic, CBPeripheral, CBPeripheralDelegate,
    CBPeripheralManager, CBPeripheralManagerDelegate, CBService, CBUUID,
};
use objc2_foundation::{
    NSArray, NSData, NSDictionary, NSError, NSNumber, NSObject, NSObjectProtocol, NSString,
};
use tokio::sync::mpsc;

use crate::{NodeId, PEAT_SERVICE_UUID};

// ============================================================================
// Event Types
// ============================================================================

/// Events from CBCentralManagerDelegate
#[derive(Debug, Clone)]
pub enum CentralEvent {
    /// Central manager state changed
    StateChanged(CentralState),
    /// Discovered a peripheral during scanning
    DiscoveredPeripheral {
        /// Peripheral identifier (UUID string)
        identifier: String,
        /// Advertised name
        name: Option<String>,
        /// RSSI in dBm
        rssi: i8,
        /// Advertisement data (raw bytes, if any)
        advertisement_data: Vec<u8>,
        /// Service UUIDs advertised by the peripheral
        service_uuids: Vec<String>,
        /// Is this a Peat node?
        is_peat_node: bool,
        /// Parsed node ID if Peat node
        node_id: Option<NodeId>,
    },
    /// Connected to a peripheral
    Connected {
        /// Peripheral identifier
        identifier: String,
    },
    /// Disconnected from a peripheral
    Disconnected {
        /// Peripheral identifier
        identifier: String,
        /// Error if disconnection was unexpected
        error: Option<String>,
    },
    /// Failed to connect to a peripheral
    ConnectionFailed {
        /// Peripheral identifier
        identifier: String,
        /// Error description
        error: String,
    },
}

/// CBCentralManager/CBPeripheralManager state
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CentralState {
    /// State unknown, update imminent
    Unknown,
    /// Bluetooth is resetting
    Resetting,
    /// Bluetooth is not supported on this device
    Unsupported,
    /// App is not authorized to use Bluetooth
    Unauthorized,
    /// Bluetooth is powered off
    PoweredOff,
    /// Bluetooth is powered on and ready
    PoweredOn,
}

impl CentralState {
    /// Convert from CBManagerState integer value
    pub(super) fn from_raw(value: isize) -> Self {
        match value {
            0 => CentralState::Unknown,
            1 => CentralState::Resetting,
            2 => CentralState::Unsupported,
            3 => CentralState::Unauthorized,
            4 => CentralState::PoweredOff,
            5 => CentralState::PoweredOn,
            _ => CentralState::Unknown,
        }
    }

    /// Check if Bluetooth is ready to use
    #[cfg(test)]
    pub(super) fn is_ready(&self) -> bool {
        matches!(self, CentralState::PoweredOn)
    }
}

/// Events from CBPeripheralDelegate (GATT client events)
#[derive(Debug, Clone)]
pub enum PeripheralEvent {
    /// Services discovered on peripheral
    ServicesDiscovered {
        identifier: String,
        error: Option<String>,
    },
    /// Characteristics discovered for a service
    CharacteristicsDiscovered {
        identifier: String,
        service_uuid: String,
        error: Option<String>,
    },
    /// Characteristic value read/updated
    CharacteristicValueUpdated {
        identifier: String,
        characteristic_uuid: String,
        value: Vec<u8>,
        error: Option<String>,
    },
    /// Characteristic value written
    CharacteristicWritten {
        identifier: String,
        characteristic_uuid: String,
        error: Option<String>,
    },
    /// Notification state changed
    NotificationStateChanged {
        identifier: String,
        characteristic_uuid: String,
        enabled: bool,
        error: Option<String>,
    },
    /// RSSI read
    RssiRead {
        identifier: String,
        rssi: i8,
        error: Option<String>,
    },
    /// MTU updated
    MtuUpdated { identifier: String, mtu: u16 },
}

/// Events from CBPeripheralManagerDelegate (GATT server events)
#[derive(Debug, Clone)]
pub enum PeripheralManagerEvent {
    /// Peripheral manager state changed
    StateChanged(CentralState),
    /// Service was added
    ServiceAdded {
        service_uuid: String,
        error: Option<String>,
    },
    /// Started advertising
    AdvertisingStarted { error: Option<String> },
    /// Central subscribed to characteristic
    CentralSubscribed {
        central_identifier: String,
        characteristic_uuid: String,
    },
    /// Central unsubscribed from characteristic
    CentralUnsubscribed {
        central_identifier: String,
        characteristic_uuid: String,
    },
    /// Received read request from central
    ReadRequest {
        request_id: u64,
        central_identifier: String,
        characteristic_uuid: String,
        offset: usize,
    },
    /// Received write request from central
    WriteRequest {
        request_id: u64,
        central_identifier: String,
        characteristic_uuid: String,
        value: Vec<u8>,
        offset: usize,
        response_needed: bool,
    },
    /// Ready to update subscribers
    ReadyToUpdateSubscribers,
}

// ============================================================================
// Objective-C Delegate Classes
// ============================================================================

/// Ivars for RustCentralManagerDelegate
pub struct CentralDelegateIvars {
    event_tx: Mutex<Option<mpsc::Sender<CentralEvent>>>,
    /// Discovered peripherals stored by identifier for later connection
    peripherals: Mutex<HashMap<String, Retained<CBPeripheral>>>,
}

impl Default for CentralDelegateIvars {
    fn default() -> Self {
        Self {
            event_tx: Mutex::new(None),
            peripherals: Mutex::new(HashMap::new()),
        }
    }
}

declare_class!(
    /// Objective-C class implementing CBCentralManagerDelegate
    pub struct RustCentralManagerDelegate;

    unsafe impl ClassType for RustCentralManagerDelegate {
        type Super = NSObject;
        type Mutability = mutability::InteriorMutable;
        const NAME: &'static str = "RustCentralManagerDelegate";
    }

    impl DeclaredClass for RustCentralManagerDelegate {
        type Ivars = CentralDelegateIvars;
    }

    unsafe impl NSObjectProtocol for RustCentralManagerDelegate {}

    unsafe impl CBCentralManagerDelegate for RustCentralManagerDelegate {
        #[method(centralManagerDidUpdateState:)]
        fn central_manager_did_update_state(&self, central: &CBCentralManager) {
            let state_raw = unsafe { central.state() };
            let state = CentralState::from_raw(state_raw.0);
            log::debug!("Central manager state changed: {:?}", state);

            if let Ok(guard) = self.ivars().event_tx.lock() {
                if let Some(tx) = guard.as_ref() {
                    let _ = tx.try_send(CentralEvent::StateChanged(state));
                }
            }
        }

        #[method(centralManager:didDiscoverPeripheral:advertisementData:RSSI:)]
        fn central_manager_did_discover_peripheral(
            &self,
            _central: &CBCentralManager,
            peripheral: &CBPeripheral,
            advertisement_data: &NSDictionary<NSString, objc2::runtime::AnyObject>,
            rssi: &NSNumber,
        ) {
            let identifier = unsafe {
                let uuid = peripheral.identifier();
                uuid.UUIDString().to_string()
            };

            let name = unsafe { peripheral.name().map(|s| s.to_string()) };
            let rssi_val = rssi.as_i8();

            // Parse service UUIDs from advertisement data
            let service_uuids = unsafe {
                Self::parse_service_uuids(advertisement_data)
            };

            // Check if Peat service UUID is present in the advertisement
            // Check full 128-bit UUID, 16-bit expanded form, and bare 16-bit form
            // CoreBluetooth may report any of these depending on how the device advertises
            let peat_uuid_full = PEAT_SERVICE_UUID.to_string().to_uppercase();
            let peat_uuid_16bit_expanded = "0000F47A-0000-1000-8000-00805F9B34FB";
            let peat_uuid_16bit_bare = "F47A";
            let has_peat_service = service_uuids.iter().any(|uuid| {
                let upper = uuid.to_uppercase();
                upper == peat_uuid_full || upper == peat_uuid_16bit_expanded || upper == peat_uuid_16bit_bare
            });

            // Check if this is a Peat node by:
            // 1. Presence of Peat service UUID (preferred, reliable)
            // 2. Name starting with "PEAT_" or "PEAT-" (fallback for compatibility)
            //    - New format: PEAT_<MESH>-<NODE_ID> (e.g., "PEAT_WEARTAK-8DD4")
            //    - Legacy format: PEAT-<NODE_ID> (e.g., "PEAT-12345678")
            let name_indicates_peat = name.as_ref().map(|n| {
                n.starts_with("PEAT_") || n.starts_with("PEAT-")
            }).unwrap_or(false);
            let is_peat_node = has_peat_service || name_indicates_peat;

            // Parse node ID from name (supports both formats)
            let node_id = name.as_ref().and_then(|n| {
                crate::config::MeshConfig::parse_device_name(n)
                    .map(|(_, node_id)| node_id)
            });

            // Log with service UUIDs for debugging
            if !service_uuids.is_empty() || name_indicates_peat {
                log::debug!(
                    "Discovered peripheral: {} ({:?}) RSSI: {} Peat: {} (service_uuid: {}, name: {}, uuids: {:?})",
                    identifier, name, rssi_val, is_peat_node, has_peat_service, name_indicates_peat, service_uuids
                );
            } else {
                log::trace!(
                    "Discovered peripheral: {} ({:?}) RSSI: {}",
                    identifier, name, rssi_val
                );
            }

            // Store the peripheral for later connection
            if let Ok(mut guard) = self.ivars().peripherals.lock() {
                guard.insert(identifier.clone(), peripheral.retain());
            }

            if let Ok(guard) = self.ivars().event_tx.lock() {
                if let Some(tx) = guard.as_ref() {
                    let _ = tx.try_send(CentralEvent::DiscoveredPeripheral {
                        identifier,
                        name,
                        rssi: rssi_val,
                        advertisement_data: Vec::new(),
                        service_uuids,
                        is_peat_node,
                        node_id,
                    });
                }
            }
        }

        #[method(centralManager:didConnectPeripheral:)]
        fn central_manager_did_connect_peripheral(
            &self,
            _central: &CBCentralManager,
            peripheral: &CBPeripheral,
        ) {
            let identifier = unsafe {
                peripheral.identifier().UUIDString().to_string()
            };
            log::info!("Connected to peripheral: {}", identifier);

            if let Ok(guard) = self.ivars().event_tx.lock() {
                if let Some(tx) = guard.as_ref() {
                    let _ = tx.try_send(CentralEvent::Connected { identifier });
                }
            }
        }

        #[method(centralManager:didDisconnectPeripheral:error:)]
        fn central_manager_did_disconnect_peripheral(
            &self,
            _central: &CBCentralManager,
            peripheral: &CBPeripheral,
            error: Option<&NSError>,
        ) {
            let identifier = unsafe {
                peripheral.identifier().UUIDString().to_string()
            };
            let error_str = error.map(|e| e.localizedDescription().to_string());
            log::info!("Disconnected from peripheral: {} (error: {:?})", identifier, error_str);

            if let Ok(guard) = self.ivars().event_tx.lock() {
                if let Some(tx) = guard.as_ref() {
                    let _ = tx.try_send(CentralEvent::Disconnected {
                        identifier,
                        error: error_str,
                    });
                }
            }
        }

        #[method(centralManager:didFailToConnectPeripheral:error:)]
        fn central_manager_did_fail_to_connect_peripheral(
            &self,
            _central: &CBCentralManager,
            peripheral: &CBPeripheral,
            error: Option<&NSError>,
        ) {
            let identifier = unsafe {
                peripheral.identifier().UUIDString().to_string()
            };
            let error_str = error
                .map(|e| e.localizedDescription().to_string())
                .unwrap_or_else(|| "Unknown error".to_string());
            log::warn!("Failed to connect to peripheral: {} ({})", identifier, error_str);

            if let Ok(guard) = self.ivars().event_tx.lock() {
                if let Some(tx) = guard.as_ref() {
                    let _ = tx.try_send(CentralEvent::ConnectionFailed {
                        identifier,
                        error: error_str,
                    });
                }
            }
        }
    }
);

impl RustCentralManagerDelegate {
    /// Create a new delegate with the given event sender
    pub fn new(event_tx: mpsc::Sender<CentralEvent>) -> Retained<Self> {
        let this = Self::alloc();
        let this = this.set_ivars(CentralDelegateIvars {
            event_tx: Mutex::new(Some(event_tx)),
            peripherals: Mutex::new(HashMap::new()),
        });
        unsafe { msg_send_id![super(this), init] }
    }

    /// Get as a protocol object for setting as delegate
    pub(super) fn as_protocol(&self) -> &ProtocolObject<dyn CBCentralManagerDelegate> {
        ProtocolObject::from_ref(self)
    }

    /// Get a stored peripheral by identifier
    pub(super) fn get_peripheral(&self, identifier: &str) -> Option<Retained<CBPeripheral>> {
        self.ivars()
            .peripherals
            .lock()
            .ok()
            .and_then(|guard| guard.get(identifier).cloned())
    }

    /// Remove a peripheral from storage
    #[allow(dead_code)] // May be needed for disconnect cleanup
    pub(super) fn remove_peripheral(&self, identifier: &str) -> Option<Retained<CBPeripheral>> {
        self.ivars()
            .peripherals
            .lock()
            .ok()
            .and_then(|mut guard| guard.remove(identifier))
    }

    /// Parse service UUIDs from CoreBluetooth advertisement data dictionary
    ///
    /// # Safety
    /// Caller must ensure advertisement_data is a valid NSDictionary pointer
    unsafe fn parse_service_uuids(
        advertisement_data: &NSDictionary<NSString, objc2::runtime::AnyObject>,
    ) -> Vec<String> {
        let mut service_uuids = Vec::new();

        // Get the service UUIDs array from the advertisement dictionary
        // Key: kCBAdvDataServiceUUIDs (CBAdvertisementDataServiceUUIDsKey)
        let uuids_obj = advertisement_data.objectForKey(CBAdvertisementDataServiceUUIDsKey);

        if let Some(uuids_any) = uuids_obj {
            // The value should be an NSArray of CBUUID objects
            // We need to cast the AnyObject to NSArray<CBUUID>
            // Use Retained::as_ptr() to get the raw pointer
            let uuids_ptr: *const objc2::runtime::AnyObject = Retained::as_ptr(&uuids_any);
            let uuids_array: &NSArray<CBUUID> = &*(uuids_ptr as *const NSArray<CBUUID>);

            for i in 0..uuids_array.len() {
                let uuid = &uuids_array[i];
                let uuid_string = uuid.UUIDString().to_string();
                service_uuids.push(uuid_string);
            }
        }

        service_uuids
    }
}

/// Ivars for RustPeripheralDelegate
pub struct PeripheralDelegateIvars {
    event_tx: Mutex<Option<mpsc::Sender<PeripheralEvent>>>,
}

impl Default for PeripheralDelegateIvars {
    fn default() -> Self {
        Self {
            event_tx: Mutex::new(None),
        }
    }
}

declare_class!(
    /// Objective-C class implementing CBPeripheralDelegate
    pub struct RustPeripheralDelegate;

    unsafe impl ClassType for RustPeripheralDelegate {
        type Super = NSObject;
        type Mutability = mutability::InteriorMutable;
        const NAME: &'static str = "RustPeripheralDelegate";
    }

    impl DeclaredClass for RustPeripheralDelegate {
        type Ivars = PeripheralDelegateIvars;
    }

    unsafe impl NSObjectProtocol for RustPeripheralDelegate {}

    unsafe impl CBPeripheralDelegate for RustPeripheralDelegate {
        #[method(peripheral:didDiscoverServices:)]
        fn peripheral_did_discover_services(
            &self,
            peripheral: &CBPeripheral,
            error: Option<&NSError>,
        ) {
            let identifier = unsafe {
                peripheral.identifier().UUIDString().to_string()
            };
            let error_str = error.map(|e| e.localizedDescription().to_string());
            log::debug!("Services discovered for {}: error={:?}", identifier, error_str);

            if let Ok(guard) = self.ivars().event_tx.lock() {
                if let Some(tx) = guard.as_ref() {
                    let _ = tx.try_send(PeripheralEvent::ServicesDiscovered {
                        identifier,
                        error: error_str,
                    });
                }
            }
        }

        #[method(peripheral:didDiscoverCharacteristicsForService:error:)]
        fn peripheral_did_discover_characteristics_for_service(
            &self,
            peripheral: &CBPeripheral,
            service: &CBService,
            error: Option<&NSError>,
        ) {
            let identifier = unsafe {
                peripheral.identifier().UUIDString().to_string()
            };
            let service_uuid = unsafe { service.UUID().UUIDString().to_string() };
            let error_str = error.map(|e| e.localizedDescription().to_string());

            if let Ok(guard) = self.ivars().event_tx.lock() {
                if let Some(tx) = guard.as_ref() {
                    let _ = tx.try_send(PeripheralEvent::CharacteristicsDiscovered {
                        identifier,
                        service_uuid,
                        error: error_str,
                    });
                }
            }
        }

        #[method(peripheral:didUpdateValueForCharacteristic:error:)]
        fn peripheral_did_update_value_for_characteristic(
            &self,
            peripheral: &CBPeripheral,
            characteristic: &CBCharacteristic,
            error: Option<&NSError>,
        ) {
            let identifier = unsafe {
                peripheral.identifier().UUIDString().to_string()
            };
            let characteristic_uuid = unsafe { characteristic.UUID().UUIDString().to_string() };
            let value = unsafe {
                characteristic.value()
                    .map(|d| d.bytes().to_vec())
                    .unwrap_or_default()
            };
            let error_str = error.map(|e| e.localizedDescription().to_string());

            if let Ok(guard) = self.ivars().event_tx.lock() {
                if let Some(tx) = guard.as_ref() {
                    let _ = tx.try_send(PeripheralEvent::CharacteristicValueUpdated {
                        identifier,
                        characteristic_uuid,
                        value,
                        error: error_str,
                    });
                }
            }
        }

        #[method(peripheral:didWriteValueForCharacteristic:error:)]
        fn peripheral_did_write_value_for_characteristic(
            &self,
            peripheral: &CBPeripheral,
            characteristic: &CBCharacteristic,
            error: Option<&NSError>,
        ) {
            let identifier = unsafe {
                peripheral.identifier().UUIDString().to_string()
            };
            let characteristic_uuid = unsafe { characteristic.UUID().UUIDString().to_string() };
            let error_str = error.map(|e| e.localizedDescription().to_string());

            if let Ok(guard) = self.ivars().event_tx.lock() {
                if let Some(tx) = guard.as_ref() {
                    let _ = tx.try_send(PeripheralEvent::CharacteristicWritten {
                        identifier,
                        characteristic_uuid,
                        error: error_str,
                    });
                }
            }
        }

        #[method(peripheral:didUpdateNotificationStateForCharacteristic:error:)]
        fn peripheral_did_update_notification_state_for_characteristic(
            &self,
            peripheral: &CBPeripheral,
            characteristic: &CBCharacteristic,
            error: Option<&NSError>,
        ) {
            let identifier = unsafe {
                peripheral.identifier().UUIDString().to_string()
            };
            let characteristic_uuid = unsafe { characteristic.UUID().UUIDString().to_string() };
            let enabled = unsafe { characteristic.isNotifying() };
            let error_str = error.map(|e| e.localizedDescription().to_string());

            if let Ok(guard) = self.ivars().event_tx.lock() {
                if let Some(tx) = guard.as_ref() {
                    let _ = tx.try_send(PeripheralEvent::NotificationStateChanged {
                        identifier,
                        characteristic_uuid,
                        enabled,
                        error: error_str,
                    });
                }
            }
        }

        #[method(peripheral:didReadRSSI:error:)]
        fn peripheral_did_read_rssi(
            &self,
            peripheral: &CBPeripheral,
            rssi: &NSNumber,
            error: Option<&NSError>,
        ) {
            let identifier = unsafe {
                peripheral.identifier().UUIDString().to_string()
            };
            let rssi_val = rssi.as_i8();
            let error_str = error.map(|e| e.localizedDescription().to_string());

            if let Ok(guard) = self.ivars().event_tx.lock() {
                if let Some(tx) = guard.as_ref() {
                    let _ = tx.try_send(PeripheralEvent::RssiRead {
                        identifier,
                        rssi: rssi_val,
                        error: error_str,
                    });
                }
            }
        }
    }
);

impl RustPeripheralDelegate {
    /// Create a new delegate with the given event sender
    pub fn new(event_tx: mpsc::Sender<PeripheralEvent>) -> Retained<Self> {
        let this = Self::alloc();
        let this = this.set_ivars(PeripheralDelegateIvars {
            event_tx: Mutex::new(Some(event_tx)),
        });
        unsafe { msg_send_id![super(this), init] }
    }

    /// Get as a protocol object for setting as delegate
    pub fn as_protocol(&self) -> &ProtocolObject<dyn CBPeripheralDelegate> {
        ProtocolObject::from_ref(self)
    }
}

/// Ivars for RustPeripheralManagerDelegate
pub struct PeripheralManagerDelegateIvars {
    event_tx: Mutex<Option<mpsc::Sender<PeripheralManagerEvent>>>,
    /// Stored characteristic values by UUID (for responding to read requests)
    characteristic_values: Mutex<HashMap<String, Vec<u8>>>,
}

impl Default for PeripheralManagerDelegateIvars {
    fn default() -> Self {
        Self {
            event_tx: Mutex::new(None),
            characteristic_values: Mutex::new(HashMap::new()),
        }
    }
}

declare_class!(
    /// Objective-C class implementing CBPeripheralManagerDelegate
    pub struct RustPeripheralManagerDelegate;

    unsafe impl ClassType for RustPeripheralManagerDelegate {
        type Super = NSObject;
        type Mutability = mutability::InteriorMutable;
        const NAME: &'static str = "RustPeripheralManagerDelegate";
    }

    impl DeclaredClass for RustPeripheralManagerDelegate {
        type Ivars = PeripheralManagerDelegateIvars;
    }

    unsafe impl NSObjectProtocol for RustPeripheralManagerDelegate {}

    unsafe impl CBPeripheralManagerDelegate for RustPeripheralManagerDelegate {
        #[method(peripheralManagerDidUpdateState:)]
        fn peripheral_manager_did_update_state(&self, peripheral: &CBPeripheralManager) {
            let state_raw = unsafe { peripheral.state() };
            let state = CentralState::from_raw(state_raw.0);
            log::debug!("Peripheral manager state changed: {:?}", state);

            if let Ok(guard) = self.ivars().event_tx.lock() {
                if let Some(tx) = guard.as_ref() {
                    let _ = tx.try_send(PeripheralManagerEvent::StateChanged(state));
                }
            }
        }

        #[method(peripheralManager:didAddService:error:)]
        fn peripheral_manager_did_add_service(
            &self,
            _peripheral: &CBPeripheralManager,
            service: &CBService,
            error: Option<&NSError>,
        ) {
            let service_uuid = unsafe { service.UUID().UUIDString().to_string() };
            let error_str = error.map(|e| e.localizedDescription().to_string());
            log::debug!("Service {} added: error={:?}", service_uuid, error_str);

            if let Ok(guard) = self.ivars().event_tx.lock() {
                if let Some(tx) = guard.as_ref() {
                    let _ = tx.try_send(PeripheralManagerEvent::ServiceAdded {
                        service_uuid,
                        error: error_str,
                    });
                }
            }
        }

        #[method(peripheralManagerDidStartAdvertising:error:)]
        fn peripheral_manager_did_start_advertising(
            &self,
            _peripheral: &CBPeripheralManager,
            error: Option<&NSError>,
        ) {
            let error_str = error.map(|e| e.localizedDescription().to_string());
            if let Some(ref e) = error_str {
                log::warn!("Advertising failed to start: {}", e);
            } else {
                log::info!("Advertising started successfully");
            }

            if let Ok(guard) = self.ivars().event_tx.lock() {
                if let Some(tx) = guard.as_ref() {
                    let _ = tx.try_send(PeripheralManagerEvent::AdvertisingStarted { error: error_str });
                }
            }
        }

        #[method(peripheralManagerIsReadyToUpdateSubscribers:)]
        fn peripheral_manager_is_ready_to_update_subscribers(
            &self,
            _peripheral: &CBPeripheralManager,
        ) {
            log::trace!("Ready to update subscribers");

            if let Ok(guard) = self.ivars().event_tx.lock() {
                if let Some(tx) = guard.as_ref() {
                    let _ = tx.try_send(PeripheralManagerEvent::ReadyToUpdateSubscribers);
                }
            }
        }

        #[method(peripheralManager:didReceiveReadRequest:)]
        fn peripheral_manager_did_receive_read_request(
            &self,
            peripheral: &CBPeripheralManager,
            request: &CBATTRequest,
        ) {
            let characteristic_uuid = unsafe {
                request.characteristic().UUID().UUIDString().to_string()
            };
            let offset = unsafe { request.offset() } as usize;
            let central_id = unsafe {
                request.central().identifier().UUIDString().to_string()
            };

            log::debug!(
                "Read request from {} for characteristic {} at offset {}",
                central_id,
                characteristic_uuid,
                offset
            );

            // Look up the characteristic value
            let response_result = if let Ok(values) = self.ivars().characteristic_values.lock() {
                if let Some(value) = values.get(&characteristic_uuid) {
                    if offset <= value.len() {
                        // Set the response value (from offset to end)
                        let response_data = &value[offset..];
                        unsafe {
                            let ns_data = NSData::with_bytes(response_data);
                            request.setValue(Some(&ns_data));
                        }
                        CBATTError::Success
                    } else {
                        CBATTError::InvalidOffset
                    }
                } else {
                    log::warn!("No value stored for characteristic {}", characteristic_uuid);
                    CBATTError::AttributeNotFound
                }
            } else {
                CBATTError::UnlikelyError
            };

            // Respond to the request
            unsafe {
                peripheral.respondToRequest_withResult(request, response_result);
            }

            // Send event to Rust code
            if let Ok(guard) = self.ivars().event_tx.lock() {
                if let Some(tx) = guard.as_ref() {
                    let _ = tx.try_send(PeripheralManagerEvent::ReadRequest {
                        request_id: 0, // CoreBluetooth handles request tracking internally
                        central_identifier: central_id,
                        characteristic_uuid,
                        offset,
                    });
                }
            }
        }

        #[method(peripheralManager:didReceiveWriteRequests:)]
        fn peripheral_manager_did_receive_write_requests(
            &self,
            peripheral: &CBPeripheralManager,
            requests: &NSArray<CBATTRequest>,
        ) {
            log::debug!("Received {} write request(s)", requests.len());

            // Process all write requests
            for i in 0..requests.len() {
                let request = &requests[i];

                let characteristic_uuid = unsafe {
                    request.characteristic().UUID().UUIDString().to_string()
                };
                let offset = unsafe { request.offset() } as usize;
                let central_id = unsafe {
                    request.central().identifier().UUIDString().to_string()
                };
                let value = unsafe {
                    request.value()
                        .map(|d| d.bytes().to_vec())
                        .unwrap_or_default()
                };

                log::debug!(
                    "Write request from {} for {} at offset {}: {} bytes",
                    central_id,
                    characteristic_uuid,
                    offset,
                    value.len()
                );

                // Store the new value
                if let Ok(mut values) = self.ivars().characteristic_values.lock() {
                    if offset == 0 {
                        // Replace entire value
                        values.insert(characteristic_uuid.clone(), value.clone());
                    } else {
                        // Append at offset (or extend if needed)
                        let entry = values.entry(characteristic_uuid.clone()).or_default();
                        if offset <= entry.len() {
                            entry.truncate(offset);
                            entry.extend_from_slice(&value);
                        } else {
                            // Pad with zeros if writing past end
                            entry.resize(offset, 0);
                            entry.extend_from_slice(&value);
                        }
                    }
                }

                // Send event to Rust code
                if let Ok(guard) = self.ivars().event_tx.lock() {
                    if let Some(tx) = guard.as_ref() {
                        let _ = tx.try_send(PeripheralManagerEvent::WriteRequest {
                            request_id: i as u64,
                            central_identifier: central_id,
                            characteristic_uuid,
                            value,
                            offset,
                            response_needed: true, // CoreBluetooth always needs a response
                        });
                    }
                }
            }

            // Respond to the first request (CoreBluetooth requirement)
            // All requests in the array share the same response
            if !requests.is_empty() {
                unsafe {
                    peripheral.respondToRequest_withResult(&requests[0], CBATTError::Success);
                }
            }
        }
    }
);

impl RustPeripheralManagerDelegate {
    /// Create a new delegate with the given event sender
    pub fn new(event_tx: mpsc::Sender<PeripheralManagerEvent>) -> Retained<Self> {
        let this = Self::alloc();
        let this = this.set_ivars(PeripheralManagerDelegateIvars {
            event_tx: Mutex::new(Some(event_tx)),
            characteristic_values: Mutex::new(HashMap::new()),
        });
        unsafe { msg_send_id![super(this), init] }
    }

    /// Get as a protocol object for setting as delegate
    pub fn as_protocol(&self) -> &ProtocolObject<dyn CBPeripheralManagerDelegate> {
        ProtocolObject::from_ref(self)
    }

    /// Set the value for a characteristic (used for read requests)
    ///
    /// This value will be returned when a central device reads the characteristic.
    pub fn set_characteristic_value(&self, characteristic_uuid: &str, value: Vec<u8>) {
        if let Ok(mut values) = self.ivars().characteristic_values.lock() {
            values.insert(characteristic_uuid.to_string(), value);
            log::debug!(
                "Set characteristic {} value ({} bytes)",
                characteristic_uuid,
                values
                    .get(characteristic_uuid)
                    .map(|v| v.len())
                    .unwrap_or(0)
            );
        }
    }

    /// Get the current value for a characteristic
    pub fn get_characteristic_value(&self, characteristic_uuid: &str) -> Option<Vec<u8>> {
        if let Ok(values) = self.ivars().characteristic_values.lock() {
            values.get(characteristic_uuid).cloned()
        } else {
            None
        }
    }
}

// ============================================================================
// Legacy compatibility - keep existing structs for channel forwarding
// ============================================================================

/// Legacy CentralDelegate for channel forwarding (used internally)
pub struct CentralDelegate {
    pub event_tx: mpsc::Sender<CentralEvent>,
}

impl CentralDelegate {
    pub fn new(event_tx: mpsc::Sender<CentralEvent>) -> Self {
        Self { event_tx }
    }
}

/// Legacy PeripheralDelegate for channel forwarding
pub struct PeripheralDelegate {
    pub event_tx: mpsc::Sender<PeripheralEvent>,
}

impl PeripheralDelegate {
    pub fn new(event_tx: mpsc::Sender<PeripheralEvent>) -> Self {
        Self { event_tx }
    }
}

/// Legacy PeripheralManagerDelegate for channel forwarding
pub struct PeripheralManagerDelegate {
    pub event_tx: mpsc::Sender<PeripheralManagerEvent>,
}

impl PeripheralManagerDelegate {
    pub fn new(event_tx: mpsc::Sender<PeripheralManagerEvent>) -> Self {
        Self { event_tx }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_central_state_from_raw() {
        assert_eq!(CentralState::from_raw(0), CentralState::Unknown);
        assert_eq!(CentralState::from_raw(4), CentralState::PoweredOff);
        assert_eq!(CentralState::from_raw(5), CentralState::PoweredOn);
        assert_eq!(CentralState::from_raw(99), CentralState::Unknown);
    }

    #[test]
    fn test_central_state_is_ready() {
        assert!(!CentralState::Unknown.is_ready());
        assert!(!CentralState::PoweredOff.is_ready());
        assert!(CentralState::PoweredOn.is_ready());
    }
}
