use std::sync::Arc;

use crate::{
    network::query_builder::{QueryBuilder, QueryParam},
    tracking::trackable::{Trackable, TrackableBase, TrackingStateCell},
};

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DeviceKind {
    Phone,
    Tablet,
    Desktop,
}

impl DeviceKind {
    pub fn from_str(value: &str) -> Option<Self> {
        match value {
            "PHONE" => Some(Self::Phone),
            "TABLET" => Some(Self::Tablet),
            "DESKTOP" => Some(Self::Desktop),
            _ => None,
        }
    }

    pub fn to_str(&self) -> &'static str {
        match self {
            Self::Phone => "PHONE",
            Self::Tablet => "TABLET",
            Self::Desktop => "DESKTOP",
        }
    }
}

const EVENT_TYPE: &str = "staticData";

#[derive(Debug)]
#[cfg_attr(test, derive(Clone, PartialEq))]
pub struct Device {
    pub(crate) trackable_base: TrackableBase,
    pub kind: DeviceKind,
}

impl Device {
    pub fn new(kind: DeviceKind) -> Self {
        return Device {
            trackable_base: TrackableBase::default(),
            kind,
        };
    }
}

impl Trackable for Device {
    fn encode_query(&self) -> String {
        QueryBuilder::with_params(vec![
            (QueryParam::Nonce, self.trackable_base.nonce().into()),
            (QueryParam::EventType, EVENT_TYPE.into()),
            (QueryParam::DeviceType, self.kind.to_str().into()),
        ])
        .build()
    }

    fn state_cell(&self) -> &Arc<TrackingStateCell> {
        self.trackable_base.state_cell()
    }
}

#[cfg(test)]
mod tests {
    use crate::utils::test_utils::remove_nonce_parameter;

    use super::*;
    use rstest::rstest;

    #[rstest]
    fn test_event_type() {
        assert_eq!("staticData", EVENT_TYPE);
    }

    #[rstest]
    #[case(DeviceKind::Phone)]
    #[case(DeviceKind::Tablet)]
    #[case(DeviceKind::Desktop)]
    fn test_new(#[case] device_kind: DeviceKind) {
        let device = Device::new(device_kind);
        assert_eq!(device_kind, device.kind);
    }

    #[rstest]
    #[case(Device::new(DeviceKind::Desktop), "DESKTOP")]
    #[case(Device::new(DeviceKind::Tablet), "TABLET")]
    #[case(Device::new(DeviceKind::Phone), "PHONE")]
    fn test_encode_query(#[case] device: Device, #[case] expected_type: &str) {
        // arrange
        #[rustfmt::skip]
        let expected = format!(
            "{}={}&{}={}",
            QueryParam::EventType.as_str(), EVENT_TYPE,
            QueryParam::DeviceType.as_str(), expected_type,
        );
        // act
        let actual = device.encode_query();
        // assert
        let actual = remove_nonce_parameter(&actual);
        assert_eq!(expected, actual);
    }
}
