use std::{
    collections::HashMap,
    time::{Duration, SystemTime},
};

use crate::{
    data::{
        assigned_variation::AssignedVariation,
        cbscores::{CBScores, ScoredVarId},
        geolocation::GeolocationBuilder,
        kcs_heat::KcsHeat,
        page_view_visit::PageViewVisit,
        personalization::Personalization,
        visitor_visits::{Visit, VisitorVisits},
        BaseData, Browser, BrowserKind, Conversion, CustomData, Device, DeviceKind, Geolocation, OperatingSystem,
        OperatingSystemKind, PageView,
    },
    datafile::{custom_data_info::CustomDataInfo, rule::RuleType},
    remote_visitor_data::dto::*,
    tracking::trackable::{Trackable, TrackingState},
    types::RemoteVisitorDataFilter,
};

pub(crate) struct RemoteVisitorData<'a> {
    pub filter: &'a RemoteVisitorDataFilter,
    pub custom_data_map: HashMap<u32, CustomData>,
    pub page_view_visits: HashMap<String, PageViewVisit>,
    pub conversions: Vec<Conversion>,
    pub experiments: HashMap<u32, AssignedVariation>,
    pub personalizations: HashMap<u32, Personalization>,
    pub device: Option<Device>,
    pub browser: Option<Browser>,
    pub operating_system: Option<OperatingSystem>,
    pub geolocation: Option<Geolocation>,
    pub visit_number: u32,
    pub visitor_visits: Option<VisitorVisits>,
    pub kcs_heat: Option<KcsHeat>,
    pub cbscores: Option<CBScores>,
    pub visitor_code: Option<String>,
}

impl<'a> RemoteVisitorData<'a> {
    pub fn new(filter: &'a RemoteVisitorDataFilter) -> Self {
        return Self {
            filter,
            custom_data_map: HashMap::new(),
            page_view_visits: HashMap::new(),
            conversions: Vec::new(),
            experiments: HashMap::new(),
            personalizations: HashMap::new(),
            device: None,
            browser: None,
            operating_system: None,
            geolocation: None,
            visit_number: 0,
            visitor_visits: None,
            kcs_heat: None,
            cbscores: None,
            visitor_code: None,
        };
    }

    pub fn mark_visitor_data_as_sent(&self, cdi: &impl CustomDataInfo) {
        for (_, cd) in self.custom_data_map.iter() {
            if !cdi.is_visitor_scope(cd.index) {
                cd.state_cell().change_state(TrackingState::Sent);
            }
        }
        for (_, pvv) in self.page_view_visits.iter() {
            pvv.page_view().state_cell().change_state(TrackingState::Sent);
        }
        for c in self.conversions.iter() {
            c.state_cell().change_state(TrackingState::Sent);
        }
        for (_, av) in self.experiments.iter() {
            av.state_cell().change_state(TrackingState::Sent);
        }
        if let Some(d) = &self.device {
            d.state_cell().change_state(TrackingState::Sent);
        }
        if let Some(b) = &self.browser {
            b.state_cell().change_state(TrackingState::Sent);
        }
        if let Some(os) = &self.operating_system {
            os.state_cell().change_state(TrackingState::Sent);
        }
        if let Some(g) = &self.geolocation {
            g.state_cell().change_state(TrackingState::Sent);
        }
    }

    pub fn collect_visitor_data_to_add(self) -> Vec<BaseData> {
        let mut data_list = Vec::with_capacity(
            self.custom_data_map.len()
                + self.page_view_visits.len()
                + self.conversions.len()
                + self.experiments.len()
                + self.personalizations.len()
                + 7,
        );
        data_list.extend(self.custom_data_map.into_values().map(|cd| BaseData::CustomData(cd)));
        data_list.extend(self.page_view_visits.into_values().map(|pvv| BaseData::PageViewVisit(pvv)));
        data_list.extend(self.conversions.into_iter().map(|c| BaseData::Conversion(c)));
        data_list.extend(self.experiments.into_values().map(|av| BaseData::AssignedVariation(av)));
        data_list.extend(self.personalizations.into_values().map(|p| BaseData::Personalization(p)));
        if let Some(device) = self.device {
            data_list.push(BaseData::Device(device));
        }
        if let Some(browser) = self.browser {
            data_list.push(BaseData::Browser(browser));
        }
        if let Some(operating_system) = self.operating_system {
            data_list.push(BaseData::OperatingSystem(operating_system));
        }
        if let Some(geolocation) = self.geolocation {
            data_list.push(BaseData::Geolocation(geolocation));
        }
        if let Some(visitor_visits) = self.visitor_visits {
            data_list.push(BaseData::VisitorVisits(visitor_visits));
        }
        if let Some(kcs_heat) = self.kcs_heat {
            data_list.push(BaseData::KcsHeat(kcs_heat));
        }
        if let Some(cbscores) = self.cbscores {
            data_list.push(BaseData::CBScores(cbscores));
        }
        return data_list;
    }

    pub fn parse(&mut self, model: RemoteVisitorDataModel) {
        self.parse_current_visit(model.current_visit);
        self.parse_previous_visits(model.previous_visits);
        self.parse_kcs_heat(model.kcs);
        self.parse_cbscores(model.cbs);
    }

    fn parse_current_visit(&mut self, visit: Option<VisitModel>) {
        if let Some(visit) = visit {
            self.parse_visit(visit, false);
        }
    }

    fn parse_previous_visits(&mut self, visits: Vec<VisitModel>) {
        let mut prev_visits = Vec::with_capacity(visits.len());
        for visit in visits {
            prev_visits.push(Visit::new(visit.time_started, visit.time_last_event));
            self.parse_visit(visit, true);
        }
        if !prev_visits.is_empty() {
            let vv = VisitorVisits::new(self.visit_number, prev_visits, SystemTime::UNIX_EPOCH, Duration::ZERO);
            self.visitor_visits = Some(vv);
        }
    }

    fn parse_visit(&mut self, visit: VisitModel, is_prev_visit: bool) {
        if self.visitor_code.is_none() {
            self.visitor_code = Some(visit.visitor_code);
        }
        self.parse_custom_data(visit.custom_data_events);
        self.parse_pages(visit.page_events);
        self.parse_experiments(visit.experiment_events);
        self.parse_personalizations(visit.personalization_events);
        self.parse_conversions(visit.conversion_events);
        self.parse_geolocation(visit.geolocation_events);
        self.parse_static_data(visit.static_data_event, is_prev_visit);
    }

    fn parse_custom_data(&mut self, events: Option<Vec<EventModel<CustomDataModel>>>) {
        events.into_iter().flatten().rev().for_each(|event| {
            let index = event.data.index;
            self.custom_data_map.entry(index).or_insert_with(|| {
                CustomData::new_with_index(index, event.data.values_count_map.into_keys().collect::<Vec<String>>())
            });
        });
    }

    fn parse_pages(&mut self, events: Option<Vec<EventModel<PageDataModel>>>) {
        events.into_iter().flatten().rev().for_each(|event| {
            if let Some(pvv) = self.page_view_visits.get_mut(&event.data.href) {
                pvv.inc_count();
            } else {
                self.page_view_visits.insert(
                    event.data.href.to_owned(),
                    PageViewVisit::new(PageView::new(event.data.href, event.data.title, vec![]), 1, Some(event.time)),
                );
            }
        });
    }

    fn parse_experiments(&mut self, events: Option<Vec<EventModel<ExperimentDataModel>>>) {
        events.into_iter().flatten().rev().for_each(|event| {
            self.experiments.entry(event.data.id).or_insert_with(|| {
                AssignedVariation::new_with_time(event.data.id, event.data.variation_id, event.time, RuleType::Unknown)
            });
        });
    }

    fn parse_personalizations(&mut self, events: Option<Vec<EventModel<PersonalizationDataModel>>>) {
        events.into_iter().flatten().rev().for_each(|event| {
            self.personalizations
                .entry(event.data.id)
                .or_insert_with(|| Personalization::new(event.data.id, event.data.variation_id, event.time));
        });
    }

    fn parse_conversions(&mut self, events: Option<Vec<EventModel<ConversionDataModel>>>) {
        events.into_iter().flatten().for_each(|event| {
            self.conversions.push(Conversion::new_with_opts_time(
                event.data.goal_id,
                crate::data::ConversionOpts::new().revenue(event.data.revenue).negative(event.data.negative),
                event.time,
            ));
        });
    }

    fn parse_geolocation(&mut self, events: Option<Vec<EventModel<GeolocationDataModel>>>) {
        if self.geolocation.is_none() {
            if let Some(last) = events.into_iter().flatten().last() {
                let g = last.data;
                self.geolocation = Some(
                    GeolocationBuilder::default().country(g.country).region(g.region).city(g.city).build().unwrap(),
                );
            }
        }
    }

    fn parse_static_data(&mut self, event: Option<EventModel<StaticDataModel>>, is_prev_visit: bool) {
        if let Some(event) = event {
            if self.visit_number == 0 {
                self.visit_number = event.data.visit_number;
                if is_prev_visit && self.visit_number > 0 {
                    self.visit_number += 1;
                }
            }

            if self.filter.device && self.device.is_none() {
                if let Some(device_type) = event.data.device_type.and_then(|dt| DeviceKind::from_str(&dt)) {
                    self.device = Some(Device::new(device_type));
                }
            }

            if self.filter.browser && self.browser.is_none() {
                if let Some(browser) = event.data.browser {
                    self.browser = Some(Browser::new(BrowserKind::from(browser.as_str()), event.data.browser_version));
                }
            }

            if self.filter.operating_system && self.operating_system.is_none() {
                if let Some(os) = event.data.os.and_then(|o| OperatingSystemKind::from_str(&o)) {
                    self.operating_system = Some(OperatingSystem::new(os));
                }
            }
        }
    }

    fn parse_kcs_heat(&mut self, kcs: Option<HashMap<u32, HashMap<u32, f64>>>) {
        self.kcs_heat = kcs.map(KcsHeat::new);
    }

    fn parse_cbscores(&mut self, cbs: Option<HashMap<u32, HashMap<u32, f64>>>) {
        self.cbscores = cbs.map(|cbs| {
            let cbs_map = cbs
                .into_iter()
                .map(|(exp_id, scored_var_entries)| {
                    let vars = scored_var_entries
                        .into_iter()
                        .map(|(var_id, score)| ScoredVarId {
                            variation_id: var_id,
                            score,
                        })
                        .collect();
                    (exp_id, vars)
                })
                .collect();
            CBScores::new(&cbs_map)
        });
    }
}
