use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::{Arc, RwLock};
use std::time::Duration;

use tokio::time::sleep;
use tracing::error;

use crate::cache::{FeatureCache, InMemoryCache};
use crate::dto::GrowthBookResponse;
use crate::env::Environment;
use crate::error::GrowthbookError;
use crate::gateway::GrowthbookGateway;
use crate::growthbook::GrowthBook;
use crate::model_public::{ExperimentResult, FeatureResult, GrowthBookAttribute};
use crate::sticky_bucket::StickyBucketService;

pub type OnFeatureUsageCallback = Arc<dyn Fn(String, FeatureResult) + Send + Sync>;
pub type OnExperimentViewedCallback = Arc<dyn Fn(ExperimentResult) + Send + Sync>;
pub type OnRefreshCallback = Arc<dyn Fn() + Send + Sync>; // Keeping it simple for now, maybe pass features later if needed

#[derive(Clone)]
pub struct GrowthBookClient {
    pub gb: Arc<RwLock<GrowthBook>>,
    pub cache: Option<Arc<dyn FeatureCache>>,
    gateway: Option<Arc<GrowthbookGateway>>,
    auto_refresh: bool,
    refresh_interval: Duration,
    pub on_feature_usage: Option<OnFeatureUsageCallback>,
    pub on_experiment_viewed: Option<OnExperimentViewedCallback>,
    pub on_refresh: Vec<OnRefreshCallback>,
    pub decryption_key: Option<String>,
}

impl Debug for GrowthBookClient {
    fn fmt(
        &self,
        f: &mut std::fmt::Formatter<'_>,
    ) -> std::fmt::Result {
        f.debug_struct("GrowthBookClient")
            .field("gb", &self.gb)
            .field("auto_refresh", &self.auto_refresh)
            .field("refresh_interval", &self.refresh_interval)
            .field("on_feature_usage", &self.on_feature_usage.is_some())
            .field("on_experiment_viewed", &self.on_experiment_viewed.is_some())
            .field("on_refresh", &self.on_refresh.len())
            .field("decryption_key", &self.decryption_key.is_some())
            .finish()
    }
}

pub struct GrowthBookClientBuilder {
    api_url: Option<String>,
    client_key: Option<String>,
    cache: Option<Arc<dyn FeatureCache>>,
    ttl: Option<Duration>,
    auto_refresh: bool,
    refresh_interval: Option<Duration>,
    attributes: Option<HashMap<String, GrowthBookAttribute>>,
    on_feature_usage: Option<OnFeatureUsageCallback>,
    on_experiment_viewed: Option<OnExperimentViewedCallback>,
    on_refresh: Vec<OnRefreshCallback>,
    features: Option<HashMap<String, crate::dto::GrowthBookFeature>>,
    decryption_key: Option<String>,
    sticky_bucket_service: Option<Arc<dyn StickyBucketService>>,
}

impl Default for GrowthBookClientBuilder {
    fn default() -> Self {
        Self::new()
    }
}

impl GrowthBookClientBuilder {
    pub fn new() -> Self {
        Self {
            api_url: None,
            client_key: None,
            cache: None,
            ttl: None,
            auto_refresh: false,
            refresh_interval: None,
            attributes: None,
            on_feature_usage: None,
            on_experiment_viewed: None,
            on_refresh: Vec::new(),
            features: None,
            decryption_key: None,
            sticky_bucket_service: None,
        }
    }

    pub fn api_url(
        mut self,
        api_url: String,
    ) -> Self {
        self.api_url = Some(api_url);
        self
    }

    pub fn client_key(
        mut self,
        client_key: String,
    ) -> Self {
        self.client_key = Some(client_key);
        self
    }

    pub fn cache(
        mut self,
        cache: Arc<dyn FeatureCache>,
    ) -> Self {
        self.cache = Some(cache);
        self
    }

    pub fn ttl(
        mut self,
        ttl: Duration,
    ) -> Self {
        self.ttl = Some(ttl);
        self
    }

    pub fn auto_refresh(
        mut self,
        auto_refresh: bool,
    ) -> Self {
        self.auto_refresh = auto_refresh;
        self
    }

    pub fn refresh_interval(
        mut self,
        interval: Duration,
    ) -> Self {
        self.refresh_interval = Some(interval);
        self
    }

    pub fn attributes(
        mut self,
        attributes: HashMap<String, GrowthBookAttribute>,
    ) -> Self {
        self.attributes = Some(attributes);
        self
    }

    pub fn on_feature_usage(
        mut self,
        callback: Box<dyn Fn(String, FeatureResult) + Send + Sync>,
    ) -> Self {
        self.on_feature_usage = Some(Arc::from(callback));
        self
    }

    pub fn on_experiment_viewed(
        mut self,
        callback: Box<dyn Fn(ExperimentResult) + Send + Sync>,
    ) -> Self {
        self.on_experiment_viewed = Some(Arc::from(callback));
        self
    }

    pub fn add_on_refresh(
        mut self,
        callback: Box<dyn Fn() + Send + Sync>,
    ) -> Self {
        self.on_refresh.push(Arc::from(callback));
        self
    }

    pub fn features(
        mut self,
        features: HashMap<String, crate::dto::GrowthBookFeature>,
    ) -> Self {
        self.features = Some(features);
        self
    }

    pub fn features_json(
        mut self,
        features_json: serde_json::Value,
    ) -> Result<Self, serde_json::Error> {
        let features: HashMap<String, crate::dto::GrowthBookFeature> = serde_json::from_value(features_json)?;
        self.features = Some(features);
        Ok(self)
    }

    pub fn decryption_key(
        mut self,
        decryption_key: String,
    ) -> Self {
        self.decryption_key = Some(decryption_key);
        self
    }

    pub fn sticky_bucket_service(
        mut self,
        sticky_bucket_service: Arc<dyn StickyBucketService>,
    ) -> Self {
        self.sticky_bucket_service = Some(sticky_bucket_service);
        self
    }

    pub async fn build(self) -> Result<GrowthBookClient, GrowthbookError> {
        // Gateway is optional now (for offline mode)
        let gateway = if let (Some(api_url), Some(client_key)) = (&self.api_url, &self.client_key) {
            Some(GrowthbookGateway::new(api_url, client_key, Duration::from_secs(10))?)
        } else {
            None
        };

        // Validate: Must have either manual features OR valid network config
        if self.features.is_none() && gateway.is_none() {
            return Err(GrowthbookError::new(
                crate::error::GrowthbookErrorCode::ConfigError,
                "Must provide either 'features' (manual) or 'api_url' + 'client_key' (network)",
            ));
        }

        let refresh_interval = self.refresh_interval.unwrap_or_else(|| {
            let seconds = Environment::u64_or_default("GB_UPDATE_INTERVAL", 60);
            Duration::from_secs(seconds)
        });

        let gateway_arc = gateway.map(Arc::new);

        let cache = self.cache.unwrap_or_else(|| {
            let ttl = self.ttl.unwrap_or(Duration::from_secs(60));
            Arc::new(InMemoryCache::new(ttl))
        });

        let client = GrowthBookClient {
            gb: Arc::new(RwLock::new(GrowthBook {
                forced_variations: None,
                features: self.features.clone().unwrap_or_default(), // Use cloned features if present
                attributes: self.attributes,
                sticky_bucket_service: self.sticky_bucket_service,
            })),
            cache: Some(cache),
            gateway: gateway_arc,
            auto_refresh: self.auto_refresh,
            refresh_interval,
            on_feature_usage: self.on_feature_usage,
            on_experiment_viewed: self.on_experiment_viewed,
            on_refresh: self.on_refresh,
            decryption_key: self.decryption_key,
        };

        // Initial load: Only when there are no manual features
        // If we have manual features, we assume they are the source of truth for start.
        if self.features.is_none() {
            client.refresh().await;
        }

        if client.auto_refresh && client.gateway.is_some() {
            client.start_auto_refresh();
        }

        Ok(client)
    }
}

impl GrowthBookClient {
    pub async fn refresh(&self) {
        if let Some(gateway) = &self.gateway {
            let cache_key = "features";

            // Try cache first
            if let Some(cache) = &self.cache {
                if let Some(response) = cache.get(cache_key).await {
                    self.update_gb(response);
                    return;
                }
            }

            // Fetch from network
            match gateway.get_features(None).await {
                Ok(response) => {
                    // Update cache
                    if let Some(cache) = &self.cache {
                        cache.set(cache_key, response.clone()).await;
                    }
                    self.update_gb(response);
                },
                Err(e) => {
                    error!("[growthbook-sdk] Failed to fetch features: {:?}", e);
                },
            }
        }
    }

    fn update_gb(
        &self,
        response: GrowthBookResponse,
    ) {
        let mut features = response.features;

        if let Some(encrypted_features) = response.encrypted_features {
            if let Some(key) = &self.decryption_key {
                match decrypt_features(&encrypted_features, key) {
                    Ok(decrypted) => {
                        if let Ok(parsed_features) = serde_json::from_str(&decrypted) {
                            features = Some(parsed_features);
                        } else {
                            error!("[growthbook-sdk] Failed to parse decrypted features");
                        }
                    },
                    Err(e) => {
                        error!("[growthbook-sdk] Failed to decrypt features: {:?}", e);
                    },
                }
            } else {
                error!("[growthbook-sdk] Encrypted features received but no decryption key provided");
            }
        }

        let mut writable_config = self.gb.write().expect("problem to create mutex for gb data");
        let attributes = writable_config.attributes.clone();
        *writable_config = GrowthBook {
            forced_variations: response.forced_variations,
            features: features.unwrap_or_default(),
            attributes,
            sticky_bucket_service: writable_config.sticky_bucket_service.clone(),
        };

        for callback in &self.on_refresh {
            callback();
        }
    }

    pub fn start_auto_refresh(&self) {
        let client = self.clone();
        tokio::spawn(async move {
            loop {
                sleep(client.refresh_interval).await;
                client.refresh().await;
            }
        });
    }

    // Keep existing new method for backward compatibility,
    // Old new: spawned a task immediately.
    pub async fn new(
        api_url: &str,
        sdk_key: &str,
        update_interval: Option<Duration>,
        _http_timeout: Option<Duration>,
    ) -> Result<Self, GrowthbookError> {
        let mut builder = GrowthBookClientBuilder::new()
            .api_url(api_url.to_string())
            .client_key(sdk_key.to_string())
            .auto_refresh(true)
            .ttl(Duration::from_secs(0)); // Disable caching for legacy new() to match old behavior

        // Legacy new doesn't support setting callbacks, so they default to None
        if let Some(interval) = update_interval {
            builder = builder.refresh_interval(interval);
        }

        builder.build().await
    }

    fn read_gb(&self) -> GrowthBook {
        match self.gb.read() {
            Ok(rw_read_guard) => (*rw_read_guard).clone(),
            Err(e) => {
                error!("{}", format!("[growthbook-sdk] problem to reading gb mutex data returning empty {:?}", e));
                GrowthBook {
                    forced_variations: None,
                    features: HashMap::new(),
                    attributes: None,
                    sticky_bucket_service: None,
                }
            },
        }
    }
    fn resolve_feature(
        &self,
        feature_name: &str,
        user_attributes: Option<Vec<GrowthBookAttribute>>,
    ) -> FeatureResult {
        let result = self.read_gb().check(feature_name, &user_attributes);

        // 1. Trigger on_feature_usage only for successful evaluations
        // Exclude: unknownFeature, prerequisite, cyclicPrerequisite
        let invalid_sources = ["unknownFeature", "prerequisite", "cyclicPrerequisite"];
        if !invalid_sources.contains(&result.source.as_str()) {
            if let Some(cb) = &self.on_feature_usage {
                cb(feature_name.to_string(), result.clone());
            }
        }

        // 2. Trigger on_experiment_viewed only if in_experiment is true
        if let Some(cb) = &self.on_experiment_viewed {
            if let Some(experiment_result) = &result.experiment_result {
                if experiment_result.in_experiment {
                    cb(experiment_result.clone());
                }
            }
        }

        result
    }
}

pub trait GrowthBookClientTrait: Debug + Send + Sync {
    fn is_on(
        &self,
        feature_name: &str,
        user_attributes: Option<Vec<GrowthBookAttribute>>,
    ) -> bool;

    fn is_off(
        &self,
        feature_name: &str,
        user_attributes: Option<Vec<GrowthBookAttribute>>,
    ) -> bool;

    fn feature_result(
        &self,
        feature_name: &str,
        user_attributes: Option<Vec<GrowthBookAttribute>>,
    ) -> FeatureResult;

    fn total_features(&self) -> usize;
}

impl GrowthBookClientTrait for GrowthBookClient {
    fn is_on(
        &self,
        feature_name: &str,
        user_attributes: Option<Vec<GrowthBookAttribute>>,
    ) -> bool {
        self.resolve_feature(feature_name, user_attributes).on
    }

    fn is_off(
        &self,
        feature_name: &str,
        user_attributes: Option<Vec<GrowthBookAttribute>>,
    ) -> bool {
        self.resolve_feature(feature_name, user_attributes).off
    }

    fn feature_result(
        &self,
        feature_name: &str,
        user_attributes: Option<Vec<GrowthBookAttribute>>,
    ) -> FeatureResult {
        self.resolve_feature(feature_name, user_attributes)
    }

    fn total_features(&self) -> usize {
        let gb_data = self.read_gb();
        gb_data.features.len()
    }
}

use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, KeyIvInit};
use base64::{engine::general_purpose, Engine as _};

type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;

fn decrypt_features(
    encrypted_features: &str,
    key: &str,
) -> Result<String, Box<dyn std::error::Error>> {
    let parts: Vec<&str> = encrypted_features.split('.').collect();
    if parts.len() != 2 {
        return Err("Invalid encrypted features format".into());
    }

    let iv = general_purpose::STANDARD.decode(parts[0])?;
    let mut ciphertext = general_purpose::STANDARD.decode(parts[1])?; // Mutable for in-place decryption
    let key_bytes = general_purpose::STANDARD.decode(key)?;

    if key_bytes.len() != 16 {
        return Err("Invalid key length".into());
    }

    let decryptor = Aes128CbcDec::new_from_slices(&key_bytes, &iv).map_err(|_| "Invalid key or IV length")?;

    // Decrypt in-place
    let plaintext_len = decryptor.decrypt_padded_mut::<Pkcs7>(&mut ciphertext).map_err(|_| "Decryption failed (padding error)")?.len();

    // Truncate to actual plaintext length (though decrypt_padded_mut returns slice, we modified vec)
    ciphertext.truncate(plaintext_len);

    Ok(String::from_utf8(ciphertext)?)
}
