use std::sync::Arc;

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

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum OperatingSystemKind {
    Windows = 0,
    Mac = 1,
    IOS = 2,
    Linux = 3,
    Android = 4,
    WindowsPhone = 5,
}

impl OperatingSystemKind {
    pub fn from_str(value: &str) -> Option<Self> {
        match value {
            "WINDOWS" => Some(Self::Windows),
            "MAC" => Some(Self::Mac),
            "IOS" => Some(Self::IOS),
            "LINUX" => Some(Self::Linux),
            "ANDROID" => Some(Self::Android),
            "WINDOWS_PHONE" => Some(Self::WindowsPhone),
            _ => None,
        }
    }

    pub fn to_str(&self) -> &'static str {
        match self {
            Self::Windows => "WINDOWS",
            Self::Mac => "MAC",
            Self::IOS => "IOS",
            Self::Linux => "LINUX",
            Self::Android => "ANDROID",
            Self::WindowsPhone => "WINDOWS_PHONE",
        }
    }
}

const EVENT_TYPE: &str = "staticData";

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

impl OperatingSystem {
    pub fn new(kind: OperatingSystemKind) -> Self {
        OperatingSystem {
            trackable_base: TrackableBase::default(),
            kind,
        }
    }
}

impl Trackable for OperatingSystem {
    fn encode_query(&self) -> String {
        QueryBuilder::with_params(vec![
            (QueryParam::Nonce, self.trackable_base.nonce().into()),
            (QueryParam::EventType, EVENT_TYPE.into()),
            (QueryParam::Os, self.kind.to_str().into()),
            (QueryParam::OsIndex, (self.kind as u8).to_string().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(OperatingSystemKind::Windows)]
    #[case(OperatingSystemKind::Mac)]
    #[case(OperatingSystemKind::IOS)]
    #[case(OperatingSystemKind::Linux)]
    #[case(OperatingSystemKind::Android)]
    #[case(OperatingSystemKind::WindowsPhone)]
    fn test_new(#[case] os_kind: OperatingSystemKind) {
        let os = OperatingSystem::new(os_kind);
        assert_eq!(os_kind, os.kind);
    }

    #[rstest]
    #[case(OperatingSystem::new(OperatingSystemKind::Windows), "WINDOWS", 0)]
    #[case(OperatingSystem::new(OperatingSystemKind::Mac), "MAC", 1)]
    #[case(OperatingSystem::new(OperatingSystemKind::IOS), "IOS", 2)]
    #[case(OperatingSystem::new(OperatingSystemKind::Linux), "LINUX", 3)]
    #[case(OperatingSystem::new(OperatingSystemKind::Android), "ANDROID", 4)]
    #[case(OperatingSystem::new(OperatingSystemKind::WindowsPhone), "WINDOWS_PHONE", 5)]
    fn test_encode_query(#[case] os: OperatingSystem, #[case] os_type: &str, #[case] os_index: u8) {
        // arrange
        #[rustfmt::skip]
        let expected = format!(
            "{}={}&{}={}&{}={}",
            QueryParam::EventType.as_str(), EVENT_TYPE,
            QueryParam::Os.as_str(), os_type,
            QueryParam::OsIndex.as_str(), os_index,
        );
        // act
        let actual = os.encode_query();
        // assert
        let actual = remove_nonce_parameter(&actual);
        assert_eq!(expected, actual);
    }
}
