use std::time::SystemTime;

use crate::{
    data::{self, UserAgent, page_view_visit::PageViewVisit},
    storage::{
        NormalWritingMode, OverwritingWritingMode, PageViewVisitWritingMode, activity_time_cell::ActivityTimeCell,
        map_storage::MapStorage, singleton_storage::SingletonStorage, vec_storage::VecStorage,
    },
    utils::version::SemVer,
};

#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub(crate) struct VisitorData {
    time_started: SystemTime,
    last_activity_time: ActivityTimeCell,
    mapping_identifier: Option<String>,
    pub legal_consent: bool,
    pub user_agent: SingletonStorage<UserAgent, NormalWritingMode>,
    pub device: SingletonStorage<data::Device, OverwritingWritingMode>,
    pub browser: SingletonStorage<data::Browser, OverwritingWritingMode>,
    pub application_version: SingletonStorage<SemVer, OverwritingWritingMode>,
    pub cookie: SingletonStorage<Box<data::Cookie>, NormalWritingMode>,
    pub operating_system: SingletonStorage<data::OperatingSystem, OverwritingWritingMode>,
    pub geolocation: SingletonStorage<Box<data::Geolocation>, OverwritingWritingMode>,
    pub kcs_heat: SingletonStorage<Box<data::kcs_heat::KcsHeat>, NormalWritingMode>,
    pub cbscores: SingletonStorage<Box<data::cbscores::CBScores>, OverwritingWritingMode>,
    pub visitor_visits: SingletonStorage<Box<data::visitor_visits::VisitorVisits>, OverwritingWritingMode>,
    pub custom_data_map: MapStorage<u32, data::CustomData, OverwritingWritingMode>,
    pub page_view_visits: MapStorage<String, PageViewVisit, PageViewVisitWritingMode>,
    pub conversions: VecStorage<data::Conversion>,
    pub assigned_variations: MapStorage<u32, data::assigned_variation::AssignedVariation, OverwritingWritingMode>,
    pub personalizations: MapStorage<u32, data::personalization::Personalization, OverwritingWritingMode>,
    pub forced_variations: MapStorage<u32, data::forced_variation::ForcedVariation, NormalWritingMode>,
    pub simulated_variations: MapStorage<String, data::simulated_variation::SimulatedVariation, NormalWritingMode>,
}

impl VisitorData {
    pub fn new() -> Self {
        let now = SystemTime::now();
        return Self {
            time_started: now,
            last_activity_time: now.into(),
            mapping_identifier: None,
            legal_consent: false,
            user_agent: SingletonStorage::new(),
            device: SingletonStorage::new(),
            browser: SingletonStorage::new(),
            cookie: SingletonStorage::new(),
            operating_system: SingletonStorage::new(),
            geolocation: SingletonStorage::new(),
            kcs_heat: SingletonStorage::new(),
            cbscores: SingletonStorage::new(),
            visitor_visits: SingletonStorage::new(),
            application_version: SingletonStorage::new(),
            custom_data_map: MapStorage::new(),
            page_view_visits: MapStorage::new(),
            conversions: VecStorage::new(),
            assigned_variations: MapStorage::new(),
            personalizations: MapStorage::new(),
            forced_variations: MapStorage::new(),
            simulated_variations: MapStorage::new(),
        };
    }

    pub fn time_started(&self) -> SystemTime {
        return self.time_started;
    }

    #[cfg(test)]
    pub fn set_time_started(&mut self, value: SystemTime) {
        self.time_started = value;
    }

    pub fn last_activity_time(&self) -> SystemTime {
        return self.last_activity_time.get();
    }

    #[cfg(test)]
    pub fn set_last_activity_time(&self, value: SystemTime) {
        self.last_activity_time.set(value);
    }

    pub fn update_last_activity_time(&self) {
        self.last_activity_time.set(SystemTime::now());
    }

    pub fn mapping_identifier(&self) -> Option<&str> {
        return self.mapping_identifier.as_deref();
    }

    pub fn set_mapping_identifier(&mut self, mi: &str) {
        if self.mapping_identifier.is_none() {
            self.mapping_identifier = Some(mi.to_owned());
        }
    }
}

// TODO: Add unit tests
