use core::borrow::Borrow;

use super::ble_client::BLEClientState;
use super::ble_remote_service::BLERemoteServiceState;
use super::{BLEReader, BLEWriter};
use crate::BLEAttribute;
use crate::{
  ble,
  utilities::{as_void_ptr, voidp_to_ref, ArcUnsafeCell, BleUuid, WeakUnsafeCell},
  BLEError, BLERemoteDescriptor, Signal,
};
use alloc::{boxed::Box, vec::Vec};
use bitflags::bitflags;
use core::ffi::c_void;
use esp_idf_svc::sys as esp_idf_sys;

bitflags! {
  #[repr(transparent)]
  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
  pub struct GattCharacteristicProperties: u8 {
    const BROADCAST = esp_idf_sys::BLE_GATT_CHR_PROP_BROADCAST as _;
    const READ = esp_idf_sys::BLE_GATT_CHR_PROP_READ as _;
    const WRITE_NO_RSP = esp_idf_sys::BLE_GATT_CHR_PROP_WRITE_NO_RSP as _;
    const WRITE = esp_idf_sys::BLE_GATT_CHR_PROP_WRITE as _;
    const NOTIFY = esp_idf_sys::BLE_GATT_CHR_PROP_NOTIFY as _;
    const INDICATE = esp_idf_sys::BLE_GATT_CHR_PROP_INDICATE as _;
    const AUTH_SIGN_WRITE = esp_idf_sys::BLE_GATT_CHR_PROP_AUTH_SIGN_WRITE as _;
    const EXTENDED =  esp_idf_sys::BLE_GATT_CHR_PROP_EXTENDED as _;
  }
}

#[allow(clippy::type_complexity)]
pub struct BLERemoteCharacteristicState {
  pub(crate) service: WeakUnsafeCell<BLERemoteServiceState>,
  pub(crate) uuid: BleUuid,
  pub handle: u16,
  end_handle: u16,
  properties: GattCharacteristicProperties,
  descriptors: Option<Vec<BLERemoteDescriptor>>,
  signal: Signal<u32>,
  on_notify: Option<Box<dyn FnMut(&[u8]) + Send + Sync>>,
}

impl BLEAttribute for BLERemoteCharacteristicState {
  fn get_client(&self) -> Option<ArcUnsafeCell<BLEClientState>> {
    match self.service.upgrade() {
      Some(x) => x.get_client(),
      None => None,
    }
  }
}

impl BLERemoteCharacteristicState {
  pub fn conn_handle(&self) -> u16 {
    match self.service.upgrade() {
      Some(x) => x.conn_handle(),
      None => esp_idf_sys::BLE_HS_CONN_HANDLE_NONE as _,
    }
  }
}

#[derive(Clone)]
pub struct BLERemoteCharacteristic {
  state: ArcUnsafeCell<BLERemoteCharacteristicState>,
}

impl BLERemoteCharacteristic {
  pub(crate) fn new(
    service: WeakUnsafeCell<BLERemoteServiceState>,
    chr: &esp_idf_sys::ble_gatt_chr,
  ) -> Self {
    Self {
      state: ArcUnsafeCell::new(BLERemoteCharacteristicState {
        service,
        uuid: BleUuid::from(chr.uuid),
        handle: chr.val_handle,
        end_handle: 0,
        properties: GattCharacteristicProperties::from_bits_truncate(chr.properties),
        descriptors: None,
        signal: Signal::new(),
        on_notify: None,
      }),
    }
  }

  pub(crate) fn state(&self) -> &BLERemoteCharacteristicState {
    &self.state
  }

  pub fn uuid(&self) -> BleUuid {
    self.state.uuid
  }

  pub fn properties(&self) -> GattCharacteristicProperties {
    self.state.properties
  }

  pub async fn get_descriptors(
    &mut self,
  ) -> Result<core::slice::IterMut<'_, BLERemoteDescriptor>, BLEError> {
    if self.state.descriptors.is_none() {
      self.state.descriptors = Some(Vec::new());

      if self.state.end_handle == 0 {
        unsafe {
          ble!(esp_idf_sys::ble_gattc_disc_all_chrs(
            self.state.conn_handle(),
            self.state.handle,
            self.state.service.upgrade().unwrap().end_handle,
            Some(Self::next_char_cb),
            as_void_ptr(self),
          ))?;
        }

        ble!(self.state.signal.wait().await)?;
      }

      if self.state.handle != self.state.end_handle {
        unsafe {
          ble!(esp_idf_sys::ble_gattc_disc_all_dscs(
            self.state.conn_handle(),
            self.state.handle,
            self.state.end_handle,
            Some(Self::descriptor_disc_cb),
            as_void_ptr(self),
          ))?;
        }
        ble!(self.state.signal.wait().await)?;
      }
    }

    Ok(self.state.descriptors.as_mut().unwrap().iter_mut())
  }

  pub async fn get_descriptor(
    &mut self,
    uuid: BleUuid,
  ) -> Result<&mut BLERemoteDescriptor, BLEError> {
    let mut iter = self.get_descriptors().await?;
    iter
      .find(|x| x.uuid() == uuid)
      .ok_or_else(|| BLEError::fail().unwrap_err())
  }

  extern "C" fn next_char_cb(
    conn_handle: u16,
    error: *const esp_idf_sys::ble_gatt_error,
    chr: *const esp_idf_sys::ble_gatt_chr,
    arg: *mut c_void,
  ) -> i32 {
    let characteristic = unsafe { voidp_to_ref::<Self>(arg) };
    if characteristic.state.conn_handle() != conn_handle {
      return 0;
    }

    let error = unsafe { &*error };
    if error.status == 0 {
      characteristic.state.end_handle = unsafe { (*chr).def_handle - 1 };
    } else if error.status == esp_idf_sys::BLE_HS_EDONE as _ {
      characteristic.state.end_handle = characteristic.state.service.upgrade().unwrap().end_handle;
    }

    characteristic.state.signal.signal(error.status as _);
    esp_idf_sys::BLE_HS_EDONE as _
  }

  extern "C" fn descriptor_disc_cb(
    conn_handle: u16,
    error: *const esp_idf_sys::ble_gatt_error,
    _chr_val_handle: u16,
    dsc: *const esp_idf_sys::ble_gatt_dsc,
    arg: *mut c_void,
  ) -> i32 {
    let characteristic = unsafe { voidp_to_ref::<Self>(arg) };
    if characteristic.state.conn_handle() != conn_handle {
      return 0;
    }

    let error = unsafe { &*error };
    let dsc = unsafe { &*dsc };

    if error.status == 0 {
      let descriptor =
        BLERemoteDescriptor::new(ArcUnsafeCell::downgrade(&characteristic.state), dsc);
      characteristic
        .state
        .descriptors
        .as_mut()
        .unwrap()
        .push(descriptor);
      return 0;
    }

    characteristic.state.signal.signal(error.status as _);
    esp_idf_sys::BLE_HS_EDONE as _
  }

  pub async fn read_value(&mut self) -> Result<Vec<u8>, BLEError> {
    let mut reader = BLEReader::new(self.state.conn_handle(), self.state.handle);
    reader.read_value().await
  }

  pub async fn write_value(&mut self, data: &[u8], response: bool) -> Result<(), BLEError> {
    let mut writer = BLEWriter::new(self.state.conn_handle(), self.state.handle);
    writer.write_value(data, response).await
  }

  pub async fn subscribe_notify(&mut self, response: bool) -> Result<(), BLEError> {
    self.set_notify(0x01, response).await
  }

  pub async fn subscribe_indicate(&mut self, response: bool) -> Result<(), BLEError> {
    self.set_notify(0x02, response).await
  }

  pub async fn unsubscribe(&mut self, response: bool) -> Result<(), BLEError> {
    self.set_notify(0x00, response).await
  }

  async fn set_notify(&mut self, val: u16, response: bool) -> Result<(), BLEError> {
    let desc = self.get_descriptor(BleUuid::from_uuid16(0x2902)).await?;
    desc.write_value(&val.to_ne_bytes(), response).await
  }

  pub fn on_notify(&mut self, callback: impl FnMut(&[u8]) + Send + Sync + 'static) -> &mut Self {
    self.state.on_notify = Some(Box::new(callback));
    self
  }

  pub fn can_notify(&self) -> bool {
    self
      .properties()
      .contains(GattCharacteristicProperties::NOTIFY)
  }

  pub fn can_indicate(&self) -> bool {
    self
      .properties()
      .contains(GattCharacteristicProperties::INDICATE)
  }

  pub fn can_read(&self) -> bool {
    self
      .properties()
      .contains(GattCharacteristicProperties::READ)
  }

  pub fn can_write(&self) -> bool {
    self
      .properties()
      .contains(GattCharacteristicProperties::WRITE)
  }

  pub fn can_write_no_response(&self) -> bool {
    self
      .properties()
      .contains(GattCharacteristicProperties::WRITE_NO_RSP)
  }

  pub fn can_broadcast(&self) -> bool {
    self
      .properties()
      .contains(GattCharacteristicProperties::BROADCAST)
  }

  pub(crate) unsafe fn notify(&mut self, om: *mut esp_idf_sys::os_mbuf) {
    if let Some(no_notify) = self.state.on_notify.as_mut() {
      let data = unsafe { core::slice::from_raw_parts((*om).om_data, (*om).om_len as _) };
      no_notify(data);
    }
  }
}

impl core::fmt::Display for BLERemoteCharacteristic {
  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
    write!(f, "BLERemoteCharacteristic[{}]", self.state.uuid)
  }
}

impl core::fmt::Debug for BLERemoteCharacteristic {
  fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
    f.debug_struct("BLERemoteCharacteristic")
      .field("uuid", &self.state.uuid)
      .field(
        "service",
        &self.state.service.upgrade().map(|svc| svc.borrow().uuid),
      )
      .field("handle", &self.state.handle)
      .field("end_handle", &self.state.end_handle)
      .field("properties", &self.state.properties)
      .finish()
  }
}
