use crate::{
    data::UserAgent,
    network::query_builder::{QueryBuilder, QueryParam},
    tracking::{trackable::Trackable, tracking_data::TrackingData},
};

pub(crate) struct DataAddingContext<'a> {
    result: &'a mut TrackingData,
    visitor_code_param: &'a str,
    user_agent: Option<&'a UserAgent>,
    count: usize,
    size: usize,
}

impl<'a> DataAddingContext<'a> {
    pub fn new(result: &'a mut TrackingData, visitor_code_param: &'a str, user_agent: Option<&'a UserAgent>) -> Self {
        Self {
            result,
            visitor_code_param,
            user_agent,
            count: 0,
            size: 0,
        }
    }

    pub fn count(&self) -> usize {
        return self.count;
    }

    pub fn size(&self) -> usize {
        return self.size;
    }

    #[inline]
    pub fn add_data(&mut self, data: &impl Trackable) {
        self.result.unsent_trackable_data.push(data.state_cell().clone());
        let line = self.form_line(data.encode_query());
        self.size += line.len() + 1; // +1 for line delimiter char
        self.result.tracking_lines.push(line);
        self.count += 1;
    }

    fn form_line(&mut self, mut line: String) -> String {
        line.push('&');
        line.push_str(self.visitor_code_param);
        if let Some(user_agent) = self.user_agent {
            let ua_param = QueryBuilder::with_params(vec![(QueryParam::UserAgent, user_agent.value.as_str().into())]).build();
            line.push('&');
            line.push_str(&ua_param);
            self.user_agent = None;
        }
        return line;
    }
}

// MARK: - Tests

#[cfg(test)]
mod tests {
    use crate::{
        data::{Device, DeviceKind},
        utils::test_utils,
    };

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

    fn make_ua(value: &str) -> Option<UserAgent> {
        Some(UserAgent::new(value.to_owned()))
    }

    #[rstest]
    #[case("vc=vc1", None, vec![], vec![])]
    #[case("vc=vc2", make_ua("ua1"), vec![], vec![])]
    #[case("vc=vc3", None, vec![DeviceKind::Desktop], vec!["eventType=staticData&deviceType=DESKTOP&vc=vc3"])]
    #[case(
        "vc=vc4", make_ua("ua2"), vec![DeviceKind::Phone],
        vec!["eventType=staticData&deviceType=PHONE&vc=vc4&userAgent=ua2"]
    )]
    #[case(
        "vc=vc5", None, vec![DeviceKind::Desktop, DeviceKind::Tablet, DeviceKind::Phone],
        vec![
            "eventType=staticData&deviceType=DESKTOP&vc=vc5",
            "eventType=staticData&deviceType=TABLET&vc=vc5",
            "eventType=staticData&deviceType=PHONE&vc=vc5",
        ]
    )]
    #[case(
        "vc=vc6", make_ua("ua3"), vec![DeviceKind::Desktop, DeviceKind::Tablet, DeviceKind::Phone],
        vec![
            "eventType=staticData&deviceType=DESKTOP&vc=vc6&userAgent=ua3",
            "eventType=staticData&deviceType=TABLET&vc=vc6",
            "eventType=staticData&deviceType=PHONE&vc=vc6",
        ]
    )]
    fn test_add_data(
        #[case] visitor_code_param: &str,
        #[case] user_agent: Option<UserAgent>,
        #[case] data_to_add: Vec<DeviceKind>,
        #[case] expected_lines: Vec<&str>,
    ) {
        // arrange
        const LINE_SIZE_CORRECTION: usize = "&nonce=".len() + 16 + 1; // +16 for nonce value, +1 for LF
        let mut result = TrackingData::new();
        let mut context = DataAddingContext::new(&mut result, visitor_code_param, user_agent.as_ref());
        // act
        for d in data_to_add {
            context.add_data(&Device::new(d));
        }
        // assert
        assert_eq!(
            expected_lines,
            Vec::from_iter(context.result.tracking_lines.iter().map(|line| test_utils::remove_nonce_parameter(line)))
        );
        assert_eq!(expected_lines.len(), context.count());
        assert_eq!(expected_lines.iter().map(|line| line.len() + LINE_SIZE_CORRECTION).sum::<usize>(), context.size());
    }
}
