use mockall::mock;
use serde_json::Value as JsonValue;
use std::collections::HashMap;
use std::sync::Arc;

use crate::{
    datafile::{
        custom_data_info::{CustomDataInfo, MockCustomDataInfo},
        datafile::{DataFile, InfoByExpId},
        datafile_manager::DataFileManager,
        experiment::Experiment,
        feature_flag::FeatureFlag,
        me_group::MeGroup,
        rule::Rule,
        segment::MockSegment,
        settings::MockSettings,
        variable::MockVariable,
        variation::Variation,
        variation_exposition::{MockVariationByExposition, VariationByExposition},
    },
    network::dto::{
        ConfigurationDto, CustomDataDto, DataFileDto, ExperimentDto, FeatureFlagDto, RuleDto, VariableDto,
        VariationByExpositionDto, VariationDto,
    },
    targeting::models::{ConditionDataModel, SegmentModel},
    utils::KRes,
};

// MARK: - DTO

pub(crate) fn make_configuration_dto(
    consent_required: bool,
    real_time_update: bool,
    data_api: &str,
    streaming_api: &str,
) -> ConfigurationDto {
    ConfigurationDto {
        data_api_domain: data_api.to_string(),
        streaming_api_domain: streaming_api.to_string(),
        consent_type: if consent_required { "REQUIRED" } else { "NOT_REQUIRED" }.to_owned(),
        real_time_update,
    }
}

pub(crate) fn make_variable_dto(key: &str, var_type: &str, value: JsonValue) -> VariableDto {
    VariableDto {
        key: key.to_string(),
        var_type: var_type.to_string(),
        value,
    }
}

pub(crate) fn make_variation_dto(key: &str, variables: Vec<VariableDto>) -> VariationDto {
    VariationDto {
        key: key.to_string(),
        name: key.to_string(),
        variables,
    }
}

pub(crate) fn make_variation_exposition_dto(key: &str, variation_id: u32, exposition: f64) -> VariationByExpositionDto {
    VariationByExpositionDto {
        variation_key: key.to_string(),
        variation_id,
        exposition,
    }
}

pub(crate) fn make_experiment_dto(id: u32, variation_by_exposition: Vec<VariationByExpositionDto>) -> ExperimentDto {
    ExperimentDto {
        id,
        variation_by_exposition,
    }
}

pub(crate) fn make_rule_dto(
    id: u32,
    // order: u32,
    rule_type: &str,
    exposition: f64,
    experiment: ExperimentDto,
    respool_time: Option<u64>,
    segment_id: Option<u32>,
) -> RuleDto {
    RuleDto {
        // order,
        id,
        rule_type: rule_type.to_string(),
        segment_id,
        exposition,
        experiment,
        respool_time,
    }
}

pub(crate) fn make_feature_flag_dto(
    id: u32,
    feature_key: &str,
    environment_enabled: bool,
    default_variation_key: &str,
    mutually_exclusive_group: Option<&str>,
    bucketing_custom_data_id: Option<u32>,
    rules: Vec<RuleDto>,
    variations: Vec<VariationDto>,
) -> FeatureFlagDto {
    FeatureFlagDto {
        id,
        feature_key: feature_key.to_string(),
        environment_enabled,
        default_variation_key: default_variation_key.to_string(),
        mutually_exclusive_group: mutually_exclusive_group.map(String::from),
        bucketing_custom_data_id,
        rules,
        variations,
    }
}

pub(crate) fn make_segment_dto(id: u32, audience_tracking: bool) -> SegmentModel {
    SegmentModel {
        id,
        audience_tracking: Some(audience_tracking),
        condition_data: ConditionDataModel {
            conditions: vec![],
            or_operators: vec![],
        },
    }
}

pub(crate) fn make_cdi_datafile_dto(cdi: Vec<CustomDataDto>) -> DataFileDto {
    DataFileDto {
        configuration: ConfigurationDto::default(),
        feature_flags: vec![],
        custom_data: cdi,
        segments: vec![],
        date_modified: 0,
        holdout: None,
    }
}

pub(crate) fn make_custom_data_dto(
    id: u32,
    index: u32,
    name: &str,
    local_only: bool,
    visitor_scope: bool,
    is_mapping_identifier: bool,
) -> CustomDataDto {
    CustomDataDto {
        id,
        index,
        name: name.to_owned(),
        local_only,
        scope: if visitor_scope { "VISITOR".to_string() } else { "VISIT".to_string() },
        is_mapping_identifier,
    }
}

// MARK: - Mocks

mock! {
    #[derive(Debug)]
    pub(crate) Variation {}

    impl Variation for Variation {
        type Variable = MockVariable;

        fn new(dto: VariationDto) -> Self;
        fn key(&self) -> &str;
        fn name(&self) -> &str;
        fn variables(&self) -> &HashMap<String, MockVariable>;
        fn is_active(&self) -> bool;
    }
}

mock! {
    #[derive(Debug)]
    pub(crate) Experiment {}

    impl Experiment for Experiment {
        type VariationByExposition = MockVariationByExposition;

        fn id(&self) -> u32;
        fn vars_by_exp(&self) -> &[Arc<MockVariationByExposition>];
        fn get_variation_by_hash(&self, hash: f64) -> Option<&'static Arc<MockVariationByExposition>>;
        fn get_variation_by_id(&self, variation_id: u32) -> Option<&'static Arc<MockVariationByExposition>>;
        fn get_variation_by_key(&self, variation_key: &str) -> KRes<&'static Arc<MockVariationByExposition>>;
    }
}

mock! {
    #[derive(Debug)]
    pub(crate) Rule {}

    impl Rule for Rule {
        type Experiment = MockExperiment;

        fn new(rule_dto: RuleDto) -> Self;
        fn id(&self) -> u32;
        fn exposition(&self) -> f64;
        fn experiment(&self) -> &MockExperiment;
        fn respool_time(&self) -> Option<u64>;
        fn segment_id(&self) -> Option<u32>;
        fn rule_type(&self) -> crate::datafile::rule::RuleType;
    }
}

mock! {
    #[derive(Debug)]
    pub(crate) FeatureFlag {}

    impl FeatureFlag for FeatureFlag {
        type Variation = MockVariation;
        type Rule = MockRule;

        fn new<C: CustomDataInfo + 'static>(feature_flag_dto: FeatureFlagDto, cdi: &C) -> Self;
        fn id(&self) -> u32;
        fn feature_key(&self) -> &str;
        fn is_environment_enabled(&self) -> bool;
        fn default_variation_key(&self) -> &str;
        fn me_group_name(&self) -> Option<&'static str>;
        fn bucketing_custom_data_index(&self) -> Option<u32>;
        fn rules(&self) -> &[MockRule];
        fn variations(&self) -> &HashMap<String, MockVariation>;

        fn get_variation(&self, key: &str) -> Option<&'static MockVariation>;
        fn get_rule(&self, rule_id: u32) -> Option<&'static MockRule>;
    }
}

mock! {
    #[derive(Debug)]
    pub(crate) MeGroup {}

    impl MeGroup for MeGroup {
        type FeatureFlag = MockFeatureFlag;

        fn new(feature_flags: Vec<Arc<MockFeatureFlag>>) -> Self;
        fn feature_flags(&self) -> &Vec<Arc<MockFeatureFlag>>;
        fn get_feature_flag_by_hash(&self, hash: f64) -> Option<&'static Arc<MockFeatureFlag>>;
    }
}

mock! {
    #[derive(Debug)]
    pub(crate) DataFile {}

    impl DataFile for DataFile {
        type FeatureFlag = MockFeatureFlag;
        type Experiment = MockExperiment;
        type Segment = MockSegment;
        type MeGroup = MockMeGroup;
        type Settings = MockSettings;
        type CustomDataInfo = MockCustomDataInfo;

        fn new(dto: DataFileDto, env: Option<String>, last_modified: Option<String>) -> Self;
        fn new_nullum(environment: Option<String>) -> Self;
        fn is_nullum(&self) -> bool;
        fn settings(&self) -> &MockSettings;
        fn last_modified(&self) -> Option<&'static str>;
        fn date_modified(&self) -> u64;
        fn custom_data_info(&self) -> &MockCustomDataInfo;
        fn feature_flags(&self) -> &HashMap<String, Arc<MockFeatureFlag>>;
        fn feature_flags_by_id(&self) -> &HashMap<u32, Arc<MockFeatureFlag>>;
        fn ordered_feature_flags(&self) -> &[Arc<MockFeatureFlag>];
        fn vars_by_id(&self) -> &HashMap<u32, Arc<dyn VariationByExposition>>;
        fn segments(&self) -> &HashMap<u32, Arc<MockSegment>>;
        fn audience_tracking_segments(&self) -> &[Arc<MockSegment>];
        fn me_groups(&self) -> &HashMap<String, MockMeGroup>;
        fn holdout(&self) -> Option<&'static MockExperiment>;
        fn has_targeted_delivery_rule(&self) -> bool;
        fn get_feature_flag(&self, feature_key: &str) -> KRes<&'static Arc<MockFeatureFlag>>;
        fn has_experiment_js_css_variable(&self, experiment_id: u32) -> bool;
        fn get_info_by_exp_id(&self, experiment_id: u32) -> Option<&'static InfoByExpId>;
    }
}

mock! {
    pub(crate) DataFileManager {}

    impl DataFileManager for DataFileManager {
        type DataFile = MockDataFile;

        async fn initialize(self: &Arc<Self>) -> KRes<()>;
        fn datafile(&self) -> arc_swap::Guard<Arc<MockDataFile>>;
        fn get_types_datafile(&self) -> Arc<crate::types::DataFile>;
    }
}
