use std::{fmt::Display, pin::Pin, time::Duration};
use btleplug::{
    api::{
        BDAddr, Central as _, Characteristic, Manager as _, Peripheral, ScanFilter,
        ValueNotification, WriteType,
    },
    platform::Manager,
};
use futures::{stream::StreamExt, Stream};
use tracing::{debug, error, trace, warn};
use uuid::{uuid, Uuid};
use super::{Exchange, Transport};
use crate::{
    info::{ConnInfo, LedgerInfo, Model},
    Error,
};
pub struct BleTransport {
    manager: Manager,
    peripherals: Vec<(LedgerInfo, btleplug::platform::Peripheral)>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BleInfo {
    name: String,
    addr: BDAddr,
}
impl Display for BleInfo {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.name)
    }
}
pub struct BleDevice {
    pub info: BleInfo,
    mtu: u8,
    p: btleplug::platform::Peripheral,
    c_write: Characteristic,
    c_read: Characteristic,
}
#[derive(Clone, PartialEq, Debug)]
struct BleSpec {
    pub model: Model,
    pub service_uuid: Uuid,
    pub notify_uuid: Uuid,
    pub write_uuid: Uuid,
    pub write_cmd_uuid: Uuid,
}
const BLE_SPECS: &[BleSpec] = &[
    BleSpec {
        model: Model::NanoX,
        service_uuid: uuid!("13d63400-2c97-0004-0000-4c6564676572"),
        notify_uuid: uuid!("13d63400-2c97-0004-0001-4c6564676572"),
        write_uuid: uuid!("13d63400-2c97-0004-0002-4c6564676572"),
        write_cmd_uuid: uuid!("13d63400-2c97-0004-0003-4c6564676572"),
    },
    BleSpec {
        model: Model::Stax,
        service_uuid: uuid!("13d63400-2c97-6004-0000-4c6564676572"),
        notify_uuid: uuid!("13d63400-2c97-6004-0001-4c6564676572"),
        write_uuid: uuid!("13d63400-2c97-6004-0002-4c6564676572"),
        write_cmd_uuid: uuid!("13d63400-2c97-6004-0003-4c6564676572"),
    },
];
impl BleTransport {
    pub async fn new() -> Result<Self, Error> {
        let manager = Manager::new().await?;
        Ok(Self {
            manager,
            peripherals: vec![],
        })
    }
    async fn scan_internal(
        &self,
        duration: Duration,
    ) -> Result<Vec<(LedgerInfo, btleplug::platform::Peripheral)>, Error> {
        let mut matched = vec![];
        let adapters = self.manager.adapters().await?;
        let f = ScanFilter { services: vec![] };
        for adapter in adapters.iter() {
            let info = adapter.adapter_info().await?;
            debug!("Scan with adapter {info}");
            adapter.start_scan(f.clone()).await?;
            tokio::time::sleep(duration).await;
            let mut peripherals = adapter.peripherals().await?;
            if peripherals.is_empty() {
                debug!("No peripherals found on adaptor {info}");
                continue;
            }
            for p in peripherals.drain(..) {
                let (properties, _connected) = (p.properties().await?, p.is_connected().await?);
                let properties = match properties {
                    Some(v) => v,
                    None => {
                        debug!("Failed to fetch properties for peripheral: {p:?}");
                        continue;
                    }
                };
                let name = match &properties.local_name {
                    Some(v) => v,
                    None => continue,
                };
                debug!("Peripheral: {p:?} props: {properties:?}");
                let model = if name.contains("Nano X") {
                    Model::NanoX
                } else if name.contains("Stax") {
                    Model::Stax
                } else {
                    continue;
                };
                matched.push((
                    LedgerInfo {
                        model: model.clone(),
                        conn: BleInfo {
                            name: name.clone(),
                            addr: properties.address,
                        }
                        .into(),
                    },
                    p,
                ));
            }
        }
        Ok(matched)
    }
}
#[cfg_attr(not(feature = "unstable_async_trait"), async_trait::async_trait)]
impl Transport for BleTransport {
    type Filters = ();
    type Info = BleInfo;
    type Device = BleDevice;
    async fn list(&mut self, _filters: Self::Filters) -> Result<Vec<LedgerInfo>, Error> {
        let devices = self.scan_internal(Duration::from_millis(1000)).await?;
        let info: Vec<_> = devices.iter().map(|d| d.0.clone()).collect();
        self.peripherals = devices;
        Ok(info)
    }
    async fn connect(&mut self, info: Self::Info) -> Result<Self::Device, Error> {
        let (d, p) = match self
            .peripherals
            .iter()
            .find(|(d, _p)| d.conn == info.clone().into())
        {
            Some(v) => v,
            None => {
                warn!("No device found matching: {info:?}");
                return Err(Error::NoDevices);
            }
        };
        let i = match &d.conn {
            ConnInfo::Ble(i) => i,
            _ => unreachable!(),
        };
        let name = &i.name;
        let properties = p.properties().await?;
        let specs = match BLE_SPECS.iter().find(|s| s.model == d.model) {
            Some(v) => v,
            None => {
                warn!("No specs for model: {:?}", d.model);
                return Err(Error::Unknown);
            }
        };
        if !p.is_connected().await? {
            if let Err(e) = p.connect().await {
                warn!("Failed to connect to {name}: {e:?}");
                return Err(Error::Unknown);
            }
            if !p.is_connected().await? {
                warn!("Not connected to {name}");
                return Err(Error::Unknown);
            }
        }
        debug!("peripheral {name}: {p:?} properties: {properties:?}");
        p.discover_services().await?;
        let characteristics = p.characteristics();
        trace!("Characteristics: {characteristics:?}");
        let c_write = characteristics.iter().find(|c| c.uuid == specs.write_uuid);
        let c_read = characteristics.iter().find(|c| c.uuid == specs.notify_uuid);
        let (c_write, c_read) = match (c_write, c_read) {
            (Some(w), Some(r)) => (w, r),
            _ => {
                error!("Failed to match read and write characteristics for {name}");
                return Err(Error::Unknown);
            }
        };
        let mut d = BleDevice {
            info: info.clone(),
            mtu: 23,
            p: p.clone(),
            c_write: c_write.clone(),
            c_read: c_read.clone(),
        };
        match d.fetch_mtu().await {
            Ok(mtu) => d.mtu = mtu,
            Err(e) => {
                warn!("Failed to fetch MTU: {:?}", e);
            }
        }
        debug!("using MTU: {}", d.mtu);
        Ok(d)
    }
}
const BLE_HEADER_LEN: usize = 3;
impl BleDevice {
    async fn write_command(&mut self, cmd: u8, payload: &[u8]) -> Result<(), Error> {
        let mut data = Vec::with_capacity(payload.len() + 2);
        data.extend_from_slice(&(payload.len() as u16).to_be_bytes()); data.extend_from_slice(payload); debug!("TX cmd: 0x{cmd:02x} payload: {data:02x?}");
        for (i, c) in data.chunks(self.mtu as usize - BLE_HEADER_LEN).enumerate() {
            let mut buff = Vec::with_capacity(self.mtu as usize);
            let cmd = match i == 0 {
                true => cmd,
                false => 0x03,
            };
            buff.push(cmd); buff.extend_from_slice(&(i as u16).to_be_bytes()); buff.extend_from_slice(c);
            debug!("Write chunk {i}: {:02x?}", buff);
            self.p
                .write(&self.c_write, &buff, WriteType::WithResponse)
                .await?;
        }
        Ok(())
    }
    async fn read_data(
        &mut self,
        mut notifications: Pin<Box<dyn Stream<Item = ValueNotification> + Send>>,
    ) -> Result<Vec<u8>, Error> {
        let v = match notifications.next().await {
            Some(v) => v.value,
            None => {
                return Err(Error::Closed);
            }
        };
        debug!("RX: {:02x?}", v);
        if v.len() < 5 {
            error!("response too short");
            return Err(Error::UnexpectedResponse);
        } else if v[0] != 0x05 {
            error!("unexpected response type: {:?}", v[0]);
            return Err(Error::UnexpectedResponse);
        }
        let len = v[4] as usize;
        if len == 0 {
            return Err(Error::EmptyResponse);
        }
        trace!("Expecting response length: {}", len);
        let mut buff = Vec::with_capacity(len);
        buff.extend_from_slice(&v[5..]);
        while buff.len() < len {
            let v = match notifications.next().await {
                Some(v) => v.value,
                None => {
                    error!("Failed to fetch next chunk from peripheral");
                    self.p.unsubscribe(&self.c_read).await?;
                    return Err(Error::Closed);
                }
            };
            debug!("RX: {v:02x?}");
            buff.extend_from_slice(&v[5..]);
        }
        Ok(buff)
    }
    async fn fetch_mtu(&mut self) -> Result<u8, Error> {
        self.p.subscribe(&self.c_read).await?;
        let mut n = self.p.notifications().await?;
        self.write_command(0x08, &[]).await?;
        let mtu = match n.next().await {
            Some(r) if r.value[0] == 0x08 && r.value.len() == 6 => {
                debug!("RX: {:02x?}", r);
                r.value[5]
            }
            Some(r) => {
                warn!("Unexpected MTU response: {r:02x?}");
                return Err(Error::Unknown);
            }
            None => {
                warn!("Failed to request MTU");
                return Err(Error::Unknown);
            }
        };
        self.p.unsubscribe(&self.c_read).await?;
        Ok(mtu)
    }
    pub(crate) async fn is_connected(&self) -> Result<bool, Error> {
        let c = self.p.is_connected().await?;
        Ok(c)
    }
}
#[cfg_attr(not(feature = "unstable_async_trait"), async_trait::async_trait)]
impl Exchange for BleDevice {
    async fn exchange(&mut self, command: &[u8], timeout: Duration) -> Result<Vec<u8>, Error> {
        self.p.subscribe(&self.c_read).await?;
        let notifications = self.p.notifications().await?;
        if let Err(e) = self.write_command(0x05, command).await {
            self.p.unsubscribe(&self.c_read).await?;
            return Err(e);
        }
        debug!("Await response");
        let buff = match tokio::time::timeout(timeout, self.read_data(notifications)).await {
            Ok(Ok(v)) => v,
            Ok(Err(e)) => {
                self.p.unsubscribe(&self.c_read).await?;
                return Err(e);
            }
            Err(e) => {
                self.p.unsubscribe(&self.c_read).await?;
                return Err(e.into());
            }
        };
        Ok(buff)
    }
}