use parking_lot::RwLockReadGuard;

use crate::{datafile::datafile::DataFile, storage::visitor_data::VisitorData, utils::version::SemVer, warn_log};

pub mod condition_wrapper;
pub mod conditions;
pub mod models;
pub mod targeting_tree;

// #[cfg_attr(test, derive(Clone, Builder))]
pub(crate) struct TargetingData<'a, D: DataFile> {
    // #[cfg_attr(test, builder(setter(into)))]
    pub datafile: &'a D,
    // #[cfg_attr(test, builder(setter(into), default = "\"vc1\""))]
    pub visitor_code: &'a str,
    // #[cfg_attr(test, builder(setter(into)))]
    pub visitor_data: &'a RwLockReadGuard<'a, VisitorData>,
    // #[cfg_attr(test, builder(setter(into), default = "None"))]
    pub campaign_id: Option<u32>,
    // #[cfg_attr(test, builder(setter(into), default = "\"sdk\""))]
    pub sdk_name: &'a str,
    // #[cfg_attr(test, builder(setter(into), default = "SemVer::new(0, 0, 1)"))]
    pub sdk_version: SemVer,
}

#[cfg(test)]
pub(crate) struct TargetingDataBuilder<'a> {
    visitor_code: &'a str,
    campaign_id: Option<u32>,
    sdk_name: &'a str,
    sdk_version: SemVer,
}

#[cfg(test)]
impl<'a> TargetingDataBuilder<'a> {
    pub fn new() -> Self {
        Self {
            visitor_code: "vc1",
            campaign_id: None,
            sdk_name: "sdk",
            sdk_version: SemVer::new(0, 0, 1),
        }
    }

    pub fn build<D: DataFile>(self, df: &'a D, vd: &'a RwLockReadGuard<'a, VisitorData>) -> TargetingData<'a, D> {
        TargetingData {
            datafile: df,
            visitor_code: self.visitor_code,
            visitor_data: vd,
            campaign_id: self.campaign_id,
            sdk_name: self.sdk_name,
            sdk_version: self.sdk_version,
        }
    }

    pub fn visitor_code(mut self, value: &'a str) -> Self {
        self.visitor_code = value;
        return self;
    }

    pub fn campaign_id_opt(mut self, value: Option<u32>) -> Self {
        self.campaign_id = value;
        return self;
    }

    // pub fn campaign_id(mut self, value: u32) -> Self {
    //     self.campaign_id = Some(value);
    //     return self;
    // }

    pub fn sdk_name(mut self, value: &'a str) -> Self {
        self.sdk_name = value;
        return self;
    }

    pub fn sdk_version(mut self, value: SemVer) -> Self {
        self.sdk_version = value;
        return self;
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum TargetingOperator {
    Undefined,
    Number(NumberTargetingOperator),
    String(StringTargetingOperator),
    Boolean(BooleanTargetingOperator),
    AmongValues,
}

impl TargetingOperator {
    pub fn from_str(value: &str) -> TargetingOperator {
        NumberTargetingOperator::from_str(value).map_or_else(
            || {
                StringTargetingOperator::from_str(value).map_or_else(
                    || {
                        BooleanTargetingOperator::from_str(value).map_or_else(
                            || match value {
                                "AMONG_VALUES" => TargetingOperator::AmongValues,
                                _ => {
                                    warn_log!("Found undefined operator: {value}");
                                    TargetingOperator::Undefined
                                }
                            },
                            |op| TargetingOperator::Boolean(op),
                        )
                    },
                    |op| TargetingOperator::String(op),
                )
            },
            |op| TargetingOperator::Number(op),
        )
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum NumberTargetingOperator {
    Equal,
    Greater,
    Lower,
}

impl NumberTargetingOperator {
    pub fn from_str(value: &str) -> Option<NumberTargetingOperator> {
        match value {
            "EQUAL" => Some(NumberTargetingOperator::Equal),
            "GREATER" => Some(NumberTargetingOperator::Greater),
            "LOWER" => Some(NumberTargetingOperator::Lower),
            _ => None,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum StringTargetingOperator {
    Exact,
    Contains,
    Regex,
}

impl StringTargetingOperator {
    pub fn from_str(value: &str) -> Option<StringTargetingOperator> {
        match value {
            "EXACT" => Some(StringTargetingOperator::Exact),
            "CONTAINS" => Some(StringTargetingOperator::Contains),
            "REGULAR_EXPRESSION" => Some(StringTargetingOperator::Regex),
            _ => None,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) enum BooleanTargetingOperator {
    IsTrue,
    IsFalse,
}

impl BooleanTargetingOperator {
    pub fn from_str(value: &str) -> Option<BooleanTargetingOperator> {
        match value {
            "TRUE" => Some(BooleanTargetingOperator::IsTrue),
            "FALSE" => Some(BooleanTargetingOperator::IsFalse),
            _ => None,
        }
    }
}
