// SPDX-License-Identifier: MPL-2.0
// Copyright (C) 2026 Petr Hodina <petr.hodina@protonmail.com>

use std::sync::atomic::{AtomicU16, Ordering};
use std::time::{Duration, Instant};

use prost::Message;
use tracing::{debug, trace};

use crate::error::{Error, Result};
use crate::pb::{
    AccelSample, AttrCell, AttrQuery, AttrResponse, ClientConfig, Envelope, EnvelopeBody, GyroSample, LightSample, MagSample,
    ProximitySample, RateConfig, Reply, RotvecSample, SuidQuery, SuidReply, Uid,
};
use crate::qmi::{
    array_payload, QmiMessage, ValueType, FLAG_IND, SSC_CONTROL_MSG_ID, SSC_REPORT_LARGE_MSG_ID, SSC_REPORT_SMALL_MSG_ID,
    TLV_REPORT_DATA, TLV_REQUEST_DATA, TLV_REQUEST_REPORT_TYPE,
};
use crate::qrtr::{QrtrSocket, SockaddrQrtr};
use crate::suid::{
    self, lookup_suid, MSG_REQ_ENABLE_REPORT_CONTINUOUS, MSG_REQ_GET_ATTRIBUTES, MSG_REQ_SUID, MSG_RESP_GET_ATTRIBUTES,
    MSG_RESP_SUID, SSC_QMI_INSTANCE, SSC_QMI_SVC_ID,
};

const DEFAULT_LOOKUP_TIMEOUT: Duration = Duration::from_secs(2);
const DEFAULT_RECV_TIMEOUT: Duration = Duration::from_secs(5);

const SSC_PROCESSOR_APSS: i32 = 1;
const SSC_SUSPEND_MODE_WAKEUP: i32 = 0;

/// libssc uses the LARGE report path (indications on QMI msg id 0x22).
/// The TLV is a `guint8` per libqmi's qmi-service-ssc.json — the value
/// fits in a single byte on the wire.
const REPORT_TYPE_LARGE: u8 = 1;

/// One observation produced by an SSC sensor stream. Field semantics match
/// the libssc proto definitions for the corresponding sensor.
#[derive(Debug, Clone)]
pub struct Sample {
    /// SSC's monotonic timestamp from `ReplyBody.timestamp` —
    /// QTimer ticks (19.2 MHz on most modern Snapdragons).
    pub raw_timestamp: u64,
    /// Inner protobuf msg_id from `ReplyBody.msg_id`. Sensor
    /// implementations use it to discriminate streaming vs config-ack
    /// payloads.
    pub msg_id: u32,
    pub values: Vec<f32>,
    /// 0 = unreliable, 3 = high accuracy.
    pub accuracy: i32,
}

impl Sample {
    pub fn timestamp_ns(&self, qtimer_hz: u64) -> u64 {
        ((self.raw_timestamp as u128 * 1_000_000_000u128) / qtimer_hz as u128) as u64
    }
}

/// One sensor instance reported by the SUID lookup service.
#[derive(Debug, Clone)]
pub struct SensorInfo {
    pub data_type: String,
    pub suid: Uid,
}

/// One attribute value parsed from `SscAttrValue`. Mirrors the optional-
/// fields-of-different-types layout: exactly one variant is set per value.
#[derive(Debug, Clone, PartialEq)]
pub enum AttrValue {
    Int(i64),
    Float(f32),
    String(String),
    Bool(bool),
    /// Nested array — used by `MOUNT_MATRIX` and similar.
    Array(Vec<AttrValue>),
    /// Value present but couldn't be decoded.
    Unknown,
}

impl AttrValue {
    fn from_proto(v: &AttrCell) -> Self {
        if let Some(s) = v.string_value.as_ref() {
            return Self::String(s.clone());
        }
        if let Some(f) = v.float_value {
            return Self::Float(f);
        }
        if let Some(i) = v.int_value {
            return Self::Int(i as i64);
        }
        if let Some(b) = v.bool_value {
            return Self::Bool(b);
        }
        if let Some(arr) = v.array.as_ref() {
            return Self::Array(arr.items.iter().map(Self::from_proto).collect());
        }
        Self::Unknown
    }

    pub fn as_string(&self) -> Option<&str> {
        if let Self::String(s) = self {
            Some(s)
        } else {
            None
        }
    }
    pub fn as_float(&self) -> Option<f32> {
        if let Self::Float(f) = self {
            Some(*f)
        } else {
            None
        }
    }
    pub fn as_int(&self) -> Option<i64> {
        if let Self::Int(i) = self {
            Some(*i)
        } else {
            None
        }
    }
    pub fn as_bool(&self) -> Option<bool> {
        if let Self::Bool(b) = self {
            Some(*b)
        } else {
            None
        }
    }
}

/// All attributes returned for a sensor by `MSG_REQ_GET_ATTRIBUTES`. The map
/// is keyed by the `ATTR_*` ID; convenience accessors cover the common ones.
#[derive(Debug, Clone, Default)]
pub struct Attributes {
    pub by_id: Vec<(i32, Vec<AttrValue>)>,
}

impl Attributes {
    pub fn get(&self, id: i32) -> Option<&[AttrValue]> {
        self.by_id.iter().find(|(k, _)| *k == id).map(|(_, v)| v.as_slice())
    }

    pub fn first_string(&self, id: i32) -> Option<&str> {
        self.get(id)?.first()?.as_string()
    }
    pub fn first_int(&self, id: i32) -> Option<i64> {
        self.get(id)?.first()?.as_int()
    }
    pub fn first_bool(&self, id: i32) -> Option<bool> {
        self.get(id)?.first()?.as_bool()
    }
    pub fn floats(&self, id: i32) -> Vec<f32> {
        self.get(id).map(|vs| vs.iter().filter_map(AttrValue::as_float).collect()).unwrap_or_default()
    }

    pub fn name(&self) -> Option<&str> {
        self.first_string(suid::ATTR_NAME)
    }
    pub fn vendor(&self) -> Option<&str> {
        self.first_string(suid::ATTR_VENDOR)
    }
    pub fn available(&self) -> Option<bool> {
        self.first_bool(suid::ATTR_AVAILABLE)
    }
    /// Sample rates the sensor advertises as supported, in Hz. Use one of
    /// these exactly when calling `subscribe` to avoid silent rate clamping.
    pub fn sample_rates(&self) -> Vec<f32> {
        let mut out = self.floats(suid::ATTR_SAMPLE_RATE);
        out.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
        out.dedup();
        out
    }
    /// Available full-scale ranges (sensor-specific units, e.g. ±g for
    /// accel, ±°/s for gyro).
    pub fn available_ranges(&self) -> Vec<f32> {
        self.floats(suid::ATTR_AVAILABLE_RANGES)
    }
    pub fn fifo_size(&self) -> Option<i64> {
        self.first_int(suid::ATTR_FIFO_SIZE)
    }
    /// 0 = continuous (streaming), 1 = on-change.
    pub fn stream_type(&self) -> Option<i64> {
        self.first_int(suid::ATTR_STREAM_TYPE)
    }
}

pub struct Client {
    sock: QrtrSocket,
    ssc_addr: SockaddrQrtr,
    tx_id: AtomicU16,
}

impl Client {
    pub fn open() -> Result<Self> {
        let sock = QrtrSocket::open()?;
        let ssc_addr = sock.lookup_service(SSC_QMI_SVC_ID, SSC_QMI_INSTANCE, DEFAULT_LOOKUP_TIMEOUT)?;
        debug!(node = ssc_addr.node(), port = ssc_addr.port(), "found SSC on QRTR");
        sock.set_recv_timeout(DEFAULT_RECV_TIMEOUT)?;
        Ok(Self { sock, ssc_addr, tx_id: AtomicU16::new(1) })
    }

    fn next_tx_id(&self) -> u16 {
        let id = self.tx_id.fetch_add(1, Ordering::Relaxed);
        if id == 0 {
            self.tx_id.fetch_add(1, Ordering::Relaxed)
        } else {
            id
        }
    }

    /// Send an SSC client envelope: pack the protobuf, wrap in QMI control
    /// message (msg_id `0x20`), send to SSC.
    fn send_envelope(&self, req: &Envelope) -> Result<()> {
        let payload = req.encode_to_vec();
        let qmi = QmiMessage::request(self.next_tx_id(), SSC_CONTROL_MSG_ID)
            .add_tlv(TLV_REQUEST_REPORT_TYPE, vec![REPORT_TYPE_LARGE], ValueType::Int)
            .add_tlv(TLV_REQUEST_DATA, payload, ValueType::Array);
        let wire = qmi.encode();
        trace!(tx_len = wire.len(), tx_hex = %hex_short(&wire), "tx datagram");
        self.sock.send_to(&wire, self.ssc_addr)
    }

    /// Look up SUIDs that implement `data_type`. Sends an SUID query to
    /// the well-known `0xABABABAB...` SUID, then waits for an indication
    /// carrying the matching `SuidReply`.
    pub fn lookup(&self, data_type: &str) -> Result<Vec<Uid>> {
        let inner = SuidQuery { data_type: data_type.to_string(), subscribe: Some(false), only_defaults: Some(false) };
        let request = Envelope {
            uid: lookup_suid(),
            msg_id: MSG_REQ_SUID,
            config: ClientConfig { processor: SSC_PROCESSOR_APSS, suspend_mode: SSC_SUSPEND_MODE_WAKEUP },
            body: EnvelopeBody { opaque: None, payload: Some(inner.encode_to_vec()), passive: Some(false) },
        };
        self.send_envelope(&request)?;

        let response = self.recv_indication_for(&lookup_suid(), MSG_RESP_SUID)?;
        let parsed = SuidReply::decode(response.as_slice())?;
        if parsed.data_type != data_type {
            // Not for us — could be a leftover from another query. Caller
            // can retry; for now treat as not-found.
            return Err(Error::NoSuchSensor(data_type.to_string()));
        }
        if parsed.uids.is_empty() {
            return Err(Error::NoSuchSensor(data_type.to_string()));
        }
        Ok(parsed.uids)
    }

    /// Query a sensor's attribute set. Sends `MSG_REQ_GET_ATTRIBUTES` with
    /// `AttrQuery{subscribe=false}` and decodes the indication payload as
    /// `AttrResponse`.
    pub fn attributes(&self, suid: &Uid) -> Result<Attributes> {
        let inner = AttrQuery { subscribe: Some(false) };
        let request = Envelope {
            uid: suid.clone(),
            msg_id: MSG_REQ_GET_ATTRIBUTES,
            config: ClientConfig { processor: SSC_PROCESSOR_APSS, suspend_mode: SSC_SUSPEND_MODE_WAKEUP },
            body: EnvelopeBody { opaque: None, payload: Some(inner.encode_to_vec()), passive: Some(false) },
        };
        self.send_envelope(&request)?;

        let body = self.recv_indication_for(suid, MSG_RESP_GET_ATTRIBUTES)?;
        let parsed = AttrResponse::decode(body.as_slice())?;
        let mut out = Attributes::default();
        for attr in parsed.entries {
            let values: Vec<AttrValue> = attr.values.items.iter().map(AttrValue::from_proto).collect();
            out.by_id.push((attr.id, values));
        }
        Ok(out)
    }

    /// Probe a list of `data_type` strings; missing entries are dropped.
    pub fn discover(&self, candidates: &[&str]) -> Result<Vec<SensorInfo>> {
        let mut out = Vec::new();
        for &dt in candidates {
            match self.lookup(dt) {
                Ok(suids) => out.extend(suids.into_iter().map(|suid| SensorInfo { data_type: dt.to_string(), suid })),
                Err(Error::NoSuchSensor(_)) | Err(Error::Timeout) => {}
                Err(e) => return Err(e),
            }
        }
        Ok(out)
    }

    /// Subscribe to continuous reporting on `suid`. Returns a [`Stream`] that
    /// decodes per-sensor measurement payloads.
    pub fn subscribe(&self, suid: Uid, sample_rate_hz: f32, sensor: SensorKind) -> Result<Stream<'_>> {
        let inner = RateConfig { sample_rate: sample_rate_hz };
        let request = Envelope {
            uid: suid.clone(),
            msg_id: MSG_REQ_ENABLE_REPORT_CONTINUOUS,
            config: ClientConfig { processor: SSC_PROCESSOR_APSS, suspend_mode: SSC_SUSPEND_MODE_WAKEUP },
            body: EnvelopeBody { opaque: None, payload: Some(inner.encode_to_vec()), passive: Some(false) },
        };
        self.send_envelope(&request)?;
        Ok(Stream { client: self, suid, sensor, buf: vec![0u8; 4096], pending: Vec::new() })
    }

    /// Block until an SSC indication carrying a matching reply arrives.
    /// Returns the inner payload bytes from the first body whose
    /// `(uid, msg_id)` matches.
    fn recv_indication_for(&self, want_uid: &Uid, want_msg_id: u32) -> Result<Vec<u8>> {
        let deadline = Instant::now() + DEFAULT_RECV_TIMEOUT;
        let mut buf = vec![0u8; 4096];
        loop {
            if Instant::now() >= deadline {
                return Err(Error::Timeout);
            }
            let (n, src) = self.sock.recv_from(&mut buf)?;
            if src.node() != self.ssc_addr.node() || src.port() != self.ssc_addr.port() {
                trace!(from_node = src.node(), from_port = src.port(), "non-SSC pkt, skipping");
                continue;
            }
            trace!(raw_len = n, raw_hex = %hex_short(&buf[..n]), "rx datagram");
            let qmi = QmiMessage::decode(&buf[..n])?;
            trace!(msg_id = qmi.msg_id, flag = qmi.flag, n_tlvs = qmi.tlvs.len(), "rx parsed");
            for tlv in &qmi.tlvs {
                trace!(ty = format_args!("0x{:02x}", tlv.ty), len = tlv.value.len(), "  tlv");
            }
            if qmi.flag != FLAG_IND {
                continue;
            }
            if qmi.msg_id != SSC_REPORT_SMALL_MSG_ID && qmi.msg_id != SSC_REPORT_LARGE_MSG_ID {
                continue;
            }
            let data_tlv = match qmi.tlv(TLV_REPORT_DATA) {
                Some(t) => t,
                None => continue,
            };
            let inner = array_payload(&data_tlv.value)?;
            let response = Reply::decode(inner)?;
            if response.uid != *want_uid {
                continue;
            }
            for body in response.bodies {
                if body.msg_id == want_msg_id {
                    return Ok(body.payload);
                }
            }
        }
    }
}

/// Which protobuf to decode incoming measurement bodies as.
#[derive(Debug, Clone, Copy)]
pub enum SensorKind {
    Accelerometer,
    Gyroscope,
    Magnetometer,
    Light,
    Proximity,
    RotationVector,
}

impl SensorKind {
    pub fn from_data_type(s: &str) -> Option<Self> {
        match s {
            "accel" | "linear_accel" | "gravity" => Some(Self::Accelerometer),
            "gyro" => Some(Self::Gyroscope),
            "mag" => Some(Self::Magnetometer),
            "ambient_light" => Some(Self::Light),
            "proximity" => Some(Self::Proximity),
            "rotv" | "rotation_vector" | "game_rotation_vector" | "geomag_rotation_vector" => Some(Self::RotationVector),
            _ => None,
        }
    }

    fn decode(self, msg: &[u8]) -> Result<(Vec<f32>, i32)> {
        match self {
            Self::Accelerometer => {
                let r = AccelSample::decode(msg)?;
                Ok((r.acceleration, r.accuracy))
            }
            Self::Gyroscope => {
                let r = GyroSample::decode(msg)?;
                Ok((r.velocity, r.accuracy))
            }
            Self::Magnetometer => {
                let r = MagSample::decode(msg)?;
                Ok((r.magnetic_field, r.accuracy))
            }
            Self::Light => {
                let r = LightSample::decode(msg)?;
                Ok((r.intensity, r.accuracy))
            }
            Self::Proximity => {
                let r = ProximitySample::decode(msg)?;
                Ok((vec![r.near as f32, r.distance as f32], r.accuracy))
            }
            Self::RotationVector => {
                let r = RotvecSample::decode(msg)?;
                Ok((r.rotation, r.accuracy))
            }
        }
    }
}

/// Iterator over samples from a single subscription.
pub struct Stream<'a> {
    client: &'a Client,
    suid: Uid,
    sensor: SensorKind,
    buf: Vec<u8>,
    pending: Vec<Sample>,
}

impl Stream<'_> {
    pub fn next_sample(&mut self) -> Result<Sample> {
        if let Some(s) = self.pending.pop() {
            return Ok(s);
        }
        loop {
            let (n, src) = self.client.sock.recv_from(&mut self.buf)?;
            if src.node() != self.client.ssc_addr.node() || src.port() != self.client.ssc_addr.port() {
                continue;
            }
            let qmi = QmiMessage::decode(&self.buf[..n])?;
            if qmi.flag != FLAG_IND {
                continue;
            }
            if qmi.msg_id != SSC_REPORT_SMALL_MSG_ID && qmi.msg_id != SSC_REPORT_LARGE_MSG_ID {
                continue;
            }
            let data_tlv = match qmi.tlv(TLV_REPORT_DATA) {
                Some(t) => t,
                None => continue,
            };
            let inner = array_payload(&data_tlv.value)?;
            let response = Reply::decode(inner)?;
            if response.uid != self.suid {
                continue;
            }
            for body in response.bodies.into_iter().rev() {
                let (values, accuracy) = match self.sensor.decode(&body.payload) {
                    Ok(v) => v,
                    Err(e) => {
                        trace!(error = %e, msg_id = body.msg_id, "decode failed, skipping");
                        continue;
                    }
                };
                self.pending.push(Sample { raw_timestamp: body.timestamp, msg_id: body.msg_id, values, accuracy });
            }
            if let Some(s) = self.pending.pop() {
                return Ok(s);
            }
        }
    }
}

/// Hex dump helper for trace logs.
fn hex_short(bytes: &[u8]) -> String {
    let n = bytes.len().min(64);
    let mut out = String::with_capacity(n * 3);
    for (i, b) in bytes[..n].iter().enumerate() {
        if i > 0 {
            out.push(' ');
        }
        out.push_str(&format!("{b:02x}"));
    }
    if bytes.len() > n {
        out.push_str(" ...");
    }
    out
}

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

    #[test]
    fn sensor_kind_from_data_type_known() {
        use SensorKind::*;
        assert!(matches!(SensorKind::from_data_type("accel"), Some(Accelerometer)));
        assert!(matches!(SensorKind::from_data_type("linear_accel"), Some(Accelerometer)));
        assert!(matches!(SensorKind::from_data_type("gravity"), Some(Accelerometer)));
        assert!(matches!(SensorKind::from_data_type("gyro"), Some(Gyroscope)));
        assert!(matches!(SensorKind::from_data_type("mag"), Some(Magnetometer)));
        assert!(matches!(SensorKind::from_data_type("ambient_light"), Some(Light)));
        assert!(matches!(SensorKind::from_data_type("proximity"), Some(Proximity)));
        assert!(matches!(SensorKind::from_data_type("rotv"), Some(RotationVector)));
        assert!(matches!(SensorKind::from_data_type("rotation_vector"), Some(RotationVector)));
        assert!(matches!(SensorKind::from_data_type("game_rotation_vector"), Some(RotationVector)));
    }

    #[test]
    fn sensor_kind_from_data_type_unknown() {
        assert!(SensorKind::from_data_type("nonsense").is_none());
        assert!(SensorKind::from_data_type("").is_none());
        // Sensors with no Rust decoder yet (humidity, step_counter, ...).
        assert!(SensorKind::from_data_type("humidity").is_none());
        assert!(SensorKind::from_data_type("step_counter").is_none());
    }

    #[test]
    fn sample_timestamp_ns_at_qtimer_19_2_mhz() {
        let s = Sample {
            raw_timestamp: 19_200_000, // exactly 1 second of qtimer ticks
            msg_id: 0,
            values: vec![],
            accuracy: 3,
        };
        assert_eq!(s.timestamp_ns(19_200_000), 1_000_000_000);
    }

    #[test]
    fn sample_timestamp_ns_handles_subsecond() {
        let s = Sample {
            raw_timestamp: 9_600_000, // half of 19.2 MHz
            msg_id: 0,
            values: vec![],
            accuracy: 3,
        };
        assert_eq!(s.timestamp_ns(19_200_000), 500_000_000);
    }

    #[test]
    fn sample_timestamp_ns_handles_large_ticks() {
        // u64 raw_timestamp * 1e9 would overflow u64 for ticks >~ 18s, so
        // the impl widens to u128 internally. Verify a multi-day timestamp
        // still gives a sensible answer.
        let one_day_in_qtimer_ticks: u64 = 19_200_000 * 86_400;
        let s = Sample { raw_timestamp: one_day_in_qtimer_ticks, msg_id: 0, values: vec![], accuracy: 3 };
        assert_eq!(s.timestamp_ns(19_200_000), 86_400 * 1_000_000_000);
    }

    #[test]
    fn attr_value_typed_accessors() {
        assert_eq!(AttrValue::Int(42).as_int(), Some(42));
        assert_eq!(AttrValue::Float(1.5).as_float(), Some(1.5));
        assert_eq!(AttrValue::String("hi".into()).as_string(), Some("hi"));
        assert_eq!(AttrValue::Bool(true).as_bool(), Some(true));
    }

    #[test]
    fn attr_value_wrong_variant_returns_none() {
        // Each accessor should only succeed for its matching variant.
        assert_eq!(AttrValue::Int(42).as_string(), None);
        assert_eq!(AttrValue::Int(42).as_float(), None);
        assert_eq!(AttrValue::String("hi".into()).as_int(), None);
        assert_eq!(AttrValue::Bool(true).as_int(), None);
        assert_eq!(AttrValue::Unknown.as_int(), None);
        assert_eq!(AttrValue::Unknown.as_string(), None);
    }

    #[test]
    fn attributes_first_string_finds_matching_id() {
        let mut attrs = Attributes::default();
        attrs.by_id.push((suid::ATTR_NAME, vec![AttrValue::String("bmi160".into())]));
        attrs.by_id.push((suid::ATTR_VENDOR, vec![AttrValue::String("bosch".into())]));
        assert_eq!(attrs.name(), Some("bmi160"));
        assert_eq!(attrs.vendor(), Some("bosch"));
        // Missing ID returns None rather than panicking.
        assert_eq!(attrs.first_string(suid::ATTR_HARDWARE_ID), None);
    }

    #[test]
    fn attributes_sample_rates_sorts_and_dedups() {
        let mut attrs = Attributes::default();
        attrs.by_id.push((
            suid::ATTR_SAMPLE_RATE,
            vec![
                AttrValue::Float(50.0),
                AttrValue::Float(10.0),
                AttrValue::Float(50.0), // duplicate — should be dropped
                AttrValue::Float(200.0),
            ],
        ));
        assert_eq!(attrs.sample_rates(), vec![10.0, 50.0, 200.0]);
    }
}
