use std::{fmt::Debug, sync::Arc};

use crate::datafile::feature_flag::FeatureFlag;

pub(crate) trait MeGroup: Send + Sync + Debug {
    type FeatureFlag: FeatureFlag;
    fn new(feature_flags: Vec<Arc<Self::FeatureFlag>>) -> Self;

    fn get_feature_flag_by_hash(&self, hash: f64) -> Option<&Arc<Self::FeatureFlag>>;

    #[cfg(test)]
    fn feature_flags(&self) -> &Vec<Arc<Self::FeatureFlag>>;
}

#[derive(Debug)]
pub(crate) struct MeGroupImpl<F: FeatureFlag> {
    feature_flags: Vec<Arc<F>>,
}

impl<F: FeatureFlag> MeGroup for MeGroupImpl<F> {
    type FeatureFlag = F;

    fn new(mut feature_flags: Vec<Arc<F>>) -> Self {
        feature_flags.sort_by(|ff1, ff2| ff1.id().cmp(&ff2.id()));
        Self { feature_flags }
    }

    fn get_feature_flag_by_hash(&self, hash: f64) -> Option<&Arc<F>> {
        if self.feature_flags.is_empty() {
            return None;
        }
        let len = self.feature_flags.len();
        let idx = ((hash * len as f64) as usize).min(len - 1);
        self.feature_flags.get(idx)
    }

    #[cfg(test)]
    fn feature_flags(&self) -> &Vec<Arc<F>> {
        &self.feature_flags
    }
}

#[cfg(test)]
mod tests {
    use crate::datafile::{
        custom_data_info::CustomDataInfoImpl, datafile_test_utils::make_feature_flag_dto,
        feature_flag::FeatureFlagImpl, rule::RuleImpl, variable::VariableImpl, variation::VariationImpl,
    };

    use super::*;
    use std::sync::Arc;

    type TestFeatureFlag = FeatureFlagImpl<VariationImpl<VariableImpl>, RuleImpl>;

    #[test]
    fn test_new_sorts_feature_flags() {
        // Arrange
        let cdi = CustomDataInfoImpl::new(vec![]);
        let ff1 = FeatureFlagImpl::new(make_feature_flag_dto(1, "A", true, "", Some("G"), None, vec![], vec![]), &cdi);
        let ff2 = FeatureFlagImpl::new(make_feature_flag_dto(2, "B", true, "", Some("G"), None, vec![], vec![]), &cdi);
        let group = MeGroupImpl::<TestFeatureFlag>::new(vec![Arc::new(ff1), Arc::new(ff2)]);
        // Act
        let ids: Vec<u32> = group.feature_flags.iter().map(|f| f.id()).collect();
        // Assert
        assert_eq!(ids, vec![1, 2], "Feature flags should be sorted by id");
    }

    #[test]
    fn test_get_feature_flag_by_hash_empty() {
        // Arrange
        let group: MeGroupImpl<TestFeatureFlag> = MeGroupImpl::new(vec![]);
        // Act
        let none_hash = group.get_feature_flag_by_hash(0.5);
        // Assert
        assert!(none_hash.is_none());
    }

    #[test]
    fn test_get_feature_flag_by_hash_highest() {
        // Arrange
        let cdi = CustomDataInfoImpl::new(vec![]);
        let ff1 = FeatureFlagImpl::new(make_feature_flag_dto(1, "A", true, "", Some("G"), None, vec![], vec![]), &cdi);
        let ff2 = FeatureFlagImpl::new(make_feature_flag_dto(2, "B", true, "", Some("G"), None, vec![], vec![]), &cdi);
        let ff3 = FeatureFlagImpl::new(make_feature_flag_dto(3, "C", true, "", Some("G"), None, vec![], vec![]), &cdi);
        let group = MeGroupImpl::<TestFeatureFlag>::new(vec![Arc::new(ff1), Arc::new(ff2), Arc::new(ff3)]);
        // hash close to 1.0 should clamp to last element
        let result = group.get_feature_flag_by_hash(0.9999).unwrap();
        assert_eq!(result.id(), 3);
    }

    #[test]
    fn test_get_feature_flag_by_hash_middle() {
        // Arrange
        let cdi = CustomDataInfoImpl::new(vec![]);
        let ff1 = FeatureFlagImpl::new(make_feature_flag_dto(1, "A", true, "", Some("G"), None, vec![], vec![]), &cdi);
        let ff2 = FeatureFlagImpl::new(make_feature_flag_dto(2, "B", true, "", Some("G"), None, vec![], vec![]), &cdi);
        let ff3 = FeatureFlagImpl::new(make_feature_flag_dto(3, "C", true, "", Some("G"), None, vec![], vec![]), &cdi);
        let ff4 = FeatureFlagImpl::new(make_feature_flag_dto(4, "D", true, "", Some("G"), None, vec![], vec![]), &cdi);
        let group =
            MeGroupImpl::<TestFeatureFlag>::new(vec![Arc::new(ff4), Arc::new(ff3), Arc::new(ff2), Arc::new(ff1)]);
        // Act
        let result_0_25 = group.get_feature_flag_by_hash(0.25).unwrap();
        let result_0_50 = group.get_feature_flag_by_hash(0.5).unwrap();
        // Assert
        assert_eq!(result_0_25.id(), 2);
        assert_eq!(result_0_50.id(), 3);
    }

    #[test]
    fn test_hash_null_one() {
        // Arrange
        let cdi = CustomDataInfoImpl::new(vec![]);
        let ff1 = FeatureFlagImpl::new(make_feature_flag_dto(1, "A", true, "", Some("G"), None, vec![], vec![]), &cdi);
        let ff2 = FeatureFlagImpl::new(make_feature_flag_dto(2, "B", true, "", Some("G"), None, vec![], vec![]), &cdi);
        let group = MeGroupImpl::<TestFeatureFlag>::new(vec![Arc::new(ff1), Arc::new(ff2)]);
        // Act
        let result_0 = group.get_feature_flag_by_hash(0.0).unwrap();
        let result_1 = group.get_feature_flag_by_hash(1.0).unwrap();
        // Assert
        assert_eq!(result_0.id(), 1);
        assert_eq!(result_1.id(), 2);
    }
}
