use std::sync::Arc;

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

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BrowserKind {
    Chrome = 0,
    InternetExplorer = 1,
    Firefox = 2,
    Safari = 3,
    Opera = 4,
    Other = 5,
}

impl From<&str> for BrowserKind {
    fn from(value: &str) -> Self {
        match value {
            "CHROME" => Self::Chrome,
            "IE" => Self::InternetExplorer,
            "FIREFOX" => Self::Firefox,
            "SAFARI" => Self::Safari,
            "OPERA" => Self::Opera,
            _ => Self::Other,
        }
    }
}

const EVENT_TYPE: &str = "staticData";

#[derive(Debug)]
#[cfg_attr(test, derive(Clone, PartialEq))]
pub struct Browser {
    pub kind: BrowserKind,
    pub version: Option<f32>,
    pub(crate) trackable_base: TrackableBase,
}

impl Browser {
    pub fn new(kind: BrowserKind, version: impl Into<Option<f32>>) -> Self {
        Browser {
            kind,
            version: version.into(),
            trackable_base: TrackableBase::default(),
        }
    }
}

impl Trackable for Browser {
    fn encode_query(&self) -> String {
        let mut qb = QueryBuilder::with_params(vec![
            (QueryParam::Nonce, self.trackable_base.nonce().into()),
            (QueryParam::EventType, EVENT_TYPE.into()),
            (QueryParam::BrowserIndex, (self.kind as u8).to_string().into()),
        ]);
        if let Some(version) = self.version {
            qb.add_owned_param(QueryParam::BrowserVersion, version.to_string());
        }
        return qb.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(BrowserKind::Chrome, Some(0.0))]
    #[case(BrowserKind::InternetExplorer, Some(1.2))]
    #[case(BrowserKind::Firefox, Some(10.4))]
    #[case(BrowserKind::Safari, None)]
    #[case(BrowserKind::Opera, None)]
    #[case(BrowserKind::Other, None)]
    fn test_new(#[case] browser_kind: BrowserKind, #[case] version: Option<f32>) {
        let browser = Browser::new(browser_kind, version);
        assert_eq!(browser_kind, browser.kind);
        assert_eq!(version, browser.version);
    }

    #[rstest]
    #[case(Browser::new(BrowserKind::Chrome, None), 0)]
    #[case(Browser::new(BrowserKind::InternetExplorer, None), 1)]
    #[case(Browser::new(BrowserKind::Firefox, None), 2)]
    #[case(Browser::new(BrowserKind::Safari, None), 3)]
    #[case(Browser::new(BrowserKind::Opera, None), 4)]
    #[case(Browser::new(BrowserKind::Other, None), 5)]
    #[case(Browser::new(BrowserKind::Chrome, 10.0), 0)]
    #[case(Browser::new(BrowserKind::Safari, Some(50.4)), 3)]
    fn test_encode_query(#[case] browser: Browser, #[case] browser_index: i32) {
        // arrange
        #[rustfmt::skip]
        let mut expected = format!(
            "{}={}&{}={}",
            QueryParam::EventType.as_str(), EVENT_TYPE,
            QueryParam::BrowserIndex.as_str(), browser_index,
        );
        if let Some(version) = browser.version {
            expected.push_str(&format!("&{}={version}", QueryParam::BrowserVersion.as_str()));
        }
        // act
        let actual = browser.encode_query();
        // assert
        let actual = remove_nonce_parameter(&actual);
        assert_eq!(expected, actual);
    }
}
