//! Type definitions for feature cache system

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Feature matrix representation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureMatrix {
    /// Feature vectors (rows = samples, cols = features)
    pub features: Vec<Vec<f32>>,
    /// Feature dimension (should be 256 for production)
    pub feature_dim: usize,
    /// Number of samples
    pub sample_count: usize,
    /// Symbol this feature matrix is for
    pub symbol: String,
    /// Timestamp when features were computed
    pub timestamp: DateTime<Utc>,
}

impl FeatureMatrix {
    /// Create new feature matrix
    pub fn new(features: Vec<Vec<f32>>, symbol: String) -> Self {
        let sample_count = features.len();
        let feature_dim = features.first().map(|f| f.len()).unwrap_or(0);

        Self {
            features,
            feature_dim,
            sample_count,
            symbol,
            timestamp: Utc::now(),
        }
    }

    /// Validate feature matrix dimensions
    pub fn validate(&self) -> Result<(), String> {
        if self.features.is_empty() {
            return Err("Feature matrix is empty".to_owned());
        }

        // Check all rows have same dimension
        let expected_dim = self.feature_dim;
        for (i, row) in self.features.iter().enumerate() {
            if row.len() != expected_dim {
                return Err(format!(
                    "Row {} has {} features, expected {}",
                    i,
                    row.len(),
                    expected_dim
                ));
            }

            // Check for NaN/Inf
            for (j, &value) in row.iter().enumerate() {
                if !value.is_finite() {
                    return Err(format!(
                        "Row {} col {} contains non-finite value: {}",
                        i, j, value
                    ));
                }
            }
        }

        Ok(())
    }
}

/// Cache metadata for tracking and invalidation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheMetadata {
    /// Symbol identifier
    pub symbol: String,
    /// Number of OHLCV bars
    pub bar_count: usize,
    /// Feature dimension (always 256 for production)
    pub feature_dim: usize,
    /// When cache entry was created
    pub created_at: DateTime<Utc>,
    /// SHA-256 hash of input OHLCV data
    pub data_hash: String,
    /// `MinIO` object key
    pub object_key: String,
    /// Feature matrix statistics
    pub stats: Option<FeatureStats>,
}

/// Feature statistics for validation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureStats {
    /// Mean of each feature
    pub means: Vec<f32>,
    /// Standard deviation of each feature
    pub std_devs: Vec<f32>,
    /// Min value of each feature
    pub mins: Vec<f32>,
    /// Max value of each feature
    pub maxs: Vec<f32>,
}

impl FeatureStats {
    /// Compute statistics from feature matrix
    pub fn from_features(features: &[Vec<f32>]) -> Self {
        if features.is_empty() {
            return Self {
                means: vec![],
                std_devs: vec![],
                mins: vec![],
                maxs: vec![],
            };
        }

        let feature_dim = features[0].len();
        let n = features.len() as f32;

        let mut means = vec![0.0; feature_dim];
        let mut mins = vec![f32::MAX; feature_dim];
        let mut maxs = vec![f32::MIN; feature_dim];

        // Compute means, mins, maxs
        for row in features {
            for (j, &value) in row.iter().enumerate() {
                means[j] += value / n;
                mins[j] = mins[j].min(value);
                maxs[j] = maxs[j].max(value);
            }
        }

        // Compute standard deviations
        let mut std_devs = vec![0.0; feature_dim];
        for row in features {
            for (j, &value) in row.iter().enumerate() {
                let diff = value - means[j];
                std_devs[j] += diff * diff / n;
            }
        }
        std_devs.iter_mut().for_each(|x| *x = x.sqrt());

        Self {
            means,
            std_devs,
            mins,
            maxs,
        }
    }
}

/// Cache entry for in-memory LRU cache
#[derive(Debug, Clone)]
pub struct FeatureCache {
    /// Feature matrix
    pub matrix: FeatureMatrix,
    /// Cache metadata
    pub metadata: CacheMetadata,
    /// Last access time
    pub last_access: DateTime<Utc>,
}

/// Cache statistics for monitoring
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CacheStats {
    /// Total cache hits
    pub hits: u64,
    /// Total cache misses
    pub misses: u64,
    /// Total invalidations
    pub invalidations: u64,
    /// Total features computed
    pub features_computed: u64,
    /// Total features loaded from cache
    pub features_loaded: u64,
    /// Cache size (number of entries)
    pub cache_size: usize,
    /// Additional metadata
    pub metadata: HashMap<String, String>,
}

impl CacheStats {
    /// Create new cache statistics
    pub fn new() -> Self {
        Self::default()
    }

    /// Record cache hit
    pub const fn record_hit(&mut self) {
        self.hits += 1;
    }

    /// Record cache miss
    pub const fn record_miss(&mut self) {
        self.misses += 1;
    }

    /// Record invalidation
    pub const fn record_invalidation(&mut self) {
        self.invalidations += 1;
    }

    /// Record features computed
    pub const fn record_computed(&mut self, count: usize) {
        self.features_computed += count as u64;
    }

    /// Record features loaded
    pub const fn record_loaded(&mut self, count: usize) {
        self.features_loaded += count as u64;
    }

    /// Calculate hit rate
    pub fn hit_rate(&self) -> f64 {
        let total = self.hits + self.misses;
        if total == 0 {
            0.0
        } else {
            self.hits as f64 / total as f64
        }
    }
}
