//! Core types for ML labeling operations
//!
//! All types use integer arithmetic for financial precision:
//! - Prices in cents (1/100 dollar)
//! - Returns in basis points (1/10000 dollar)
//! - Time in nanoseconds
//! - Quantities in fixed-point representation

use serde::{Deserialize, Serialize};

/// Configuration for triple barrier labeling
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BarrierConfig {
    /// Profit target in basis points
    pub profit_target_bps: u32,
    /// Stop loss in basis points
    pub stop_loss_bps: u32,
    /// Maximum holding period in nanoseconds
    pub max_holding_period_ns: u64,
    /// Minimum return threshold in basis points
    pub min_return_threshold_bps: i32,
    /// Whether to use sample weights
    pub use_sample_weights: bool,
    /// Volatility lookback periods
    pub volatility_lookback_periods: Option<usize>,
}

impl BarrierConfig {
    /// Conservative configuration
    pub const fn conservative() -> Self {
        Self {
            profit_target_bps: 100,
            stop_loss_bps: 50,
            max_holding_period_ns: 3_600_000_000_000, // 1 hour
            min_return_threshold_bps: 10,
            use_sample_weights: true,
            volatility_lookback_periods: Some(20),
        }
    }

    /// Validate configuration
    pub fn validate(&self) -> Result<(), String> {
        if self.stop_loss_bps >= self.profit_target_bps {
            return Err("Stop loss should be less than profit target".to_owned());
        }
        Ok(())
    }
}

/// Which barrier was touched first
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BarrierTouchedFirst {
    /// Profit taking barrier (upper)
    Upper,
    /// Stop loss barrier (lower)
    Lower,
    /// Time limit reached
    TimeExpiry,
}

/// Result of barrier analysis
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum BarrierResult {
    /// Profit target was hit
    ProfitTarget,
    /// Stop loss was hit
    StopLoss,
    /// Time expiry
    TimeExpiry,
}

/// Event label for ML training
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventLabel {
    /// Event timestamp in nanoseconds
    pub event_timestamp_ns: u64,
    /// Entry price in cents
    pub entry_price_cents: u64,
    /// Barrier analysis result
    pub barrier_result: BarrierResult,
    /// Label value (-1, 0, 1)
    pub label_value: i8,
    /// Return in basis points
    pub return_bps: i32,
    /// Label quality score (0.0 to 1.0)
    pub quality_score: f64,
    /// Processing latency in microseconds
    pub processing_latency_us: u32,
}

impl EventLabel {
    /// Create new event label
    pub const fn new(
        event_timestamp_ns: u64,
        entry_price_cents: u64,
        barrier_result: BarrierResult,
        label_value: i8,
        return_bps: i32,
        quality_score: f64,
        processing_latency_us: u32,
    ) -> Self {
        Self {
            event_timestamp_ns,
            entry_price_cents,
            barrier_result,
            label_value,
            return_bps,
            quality_score,
            processing_latency_us,
        }
    }

    /// Check if the label is profitable
    pub const fn is_profitable(&self) -> bool {
        self.return_bps > 0
    }

    /// Check if processing meets latency target
    pub const fn meets_latency_target(&self, target_us: u32) -> bool {
        self.processing_latency_us <= target_us
    }

    /// Return as ratio (e.g., 0.05 for 5%)
    pub fn return_as_ratio(&self) -> f64 {
        self.return_bps as f64 / 10000.0
    }
}

/// Statistics for labeling process
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct LabelingStatistics {
    /// Total number of events processed
    pub total_events: usize,
    /// Number of positive labels
    pub positive_labels: usize,
    /// Number of negative labels
    pub negative_labels: usize,
    /// Number of neutral labels
    pub neutral_labels: usize,
    /// Average return in basis points
    pub average_return_bps: f64,
    /// Barrier touch counts [profit, loss, time]
    pub barrier_touch_counts: [usize; 3],
    /// Quality distribution [low, medium, high]
    pub quality_distribution: [usize; 3],
}

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

    /// Update statistics with new label
    pub fn update(&mut self, label: &EventLabel) {
        self.total_events += 1;

        match label.label_value {
            1 => self.positive_labels += 1,
            -1 => self.negative_labels += 1,
            _ => self.neutral_labels += 1,
        }

        // Update running average
        let new_return = label.return_bps as f64;
        self.average_return_bps = (self.average_return_bps * (self.total_events - 1) as f64
            + new_return)
            / self.total_events as f64;

        // Update barrier touch counts
        match label.barrier_result {
            BarrierResult::ProfitTarget => self.barrier_touch_counts[0] += 1,
            BarrierResult::StopLoss => self.barrier_touch_counts[1] += 1,
            BarrierResult::TimeExpiry => self.barrier_touch_counts[2] += 1,
        }

        // Update quality distribution
        let quality_index = if label.quality_score < 0.4 {
            0 // low
        } else if label.quality_score < 0.7 {
            1 // medium
        } else {
            2 // high
        };
        self.quality_distribution[quality_index] += 1;
    }
}

/// Meta-labeling configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetaLabelConfig {
    pub confidence_threshold: f64,
    pub prediction_horizon: usize,
    pub use_ensemble: bool,
}

impl Default for MetaLabelConfig {
    fn default() -> Self {
        Self {
            confidence_threshold: 0.6,
            prediction_horizon: 10,
            use_ensemble: true,
        }
    }
}

/// Meta label result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetaLabel {
    pub timestamp_ns: u64,
    pub confidence: f64,
    pub prediction: i8,
    pub bet_size: f64,
    pub expected_return: f64,
}

impl MetaLabel {
    pub const fn new(
        timestamp_ns: u64,
        confidence: f64,
        prediction: i8,
        bet_size: f64,
        expected_return: f64,
    ) -> Self {
        Self {
            timestamp_ns,
            confidence,
            prediction,
            bet_size,
            expected_return,
        }
    }
}

/// Meta labeling result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetaLabelResult {
    pub timestamp_ns: u64,
    pub meta_label: MetaLabel,
    pub processing_latency_us: u32,
}

/// Fractional differentiation configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FractionalDiffConfig {
    pub diff_order: f64,
    pub max_lags: usize,
    pub min_window_size: usize,
    pub threshold: f64,
}

impl FractionalDiffConfig {
    pub const fn standard() -> Self {
        Self {
            diff_order: 0.5,
            max_lags: 50,
            min_window_size: 5,
            threshold: 1e-6,
        }
    }
}

/// Fractional differentiation result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FractionalDiffResult {
    pub timestamp_ns: u64,
    pub original_value: i64,
    pub diff_value: i64,
    pub diff_order: f64,
    pub window_size: usize,
    pub processing_latency_us: u32,
}

/// Weighted sample for training
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WeightedSample {
    pub timestamp_ns: u64,
    pub features: Vec<f64>,
    pub label: i8,
    pub weight: f64,
    pub sample_id: Option<String>,
}

impl WeightedSample {
    pub const fn new(timestamp_ns: u64, features: Vec<f64>, label: i8, weight: f64) -> Self {
        Self {
            timestamp_ns,
            features,
            label,
            weight,
            sample_id: None,
        }
    }
}

/// Weighting configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WeightingConfig {
    pub method: WeightingMethod,
    pub volatility_window: usize,
    pub return_window: usize,
    pub time_decay_factor: f64,
}

/// Weighting methods
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum WeightingMethod {
    Uniform,
    VolatilityBased,
    ReturnBased,
    TimeDecay,
    Combined,
}

impl Default for WeightingConfig {
    fn default() -> Self {
        Self {
            method: WeightingMethod::Combined,
            volatility_window: 20,
            return_window: 10,
            time_decay_factor: 0.95,
        }
    }
}

/// Sample weighting result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WeightingResult {
    pub timestamp_ns: u64,
    pub weight: f64,
    pub method_used: WeightingMethod,
    pub processing_latency_us: u32,
}

#[cfg(test)]
#[allow(clippy::inconsistent_digit_grouping, clippy::assertions_on_result_states)]
mod tests {
    use super::*;

    #[test]
    fn test_barrier_config_validation() {
        let valid_config = BarrierConfig::conservative();
        assert!(valid_config.validate().is_ok());

        let invalid_config = BarrierConfig {
            profit_target_bps: 50,
            stop_loss_bps: 100, // Stop loss > profit target
            max_holding_period_ns: 1000,
            min_return_threshold_bps: 1,
            use_sample_weights: true,
            volatility_lookback_periods: Some(20),
        };
        assert!(invalid_config.validate().is_err());
    }

    #[test]
    fn test_event_label_creation() {
        let barrier_result = BarrierResult::ProfitTarget;

        let label = EventLabel::new(
            1692000000_000_000_000 - 3600_000_000_000, // 1 hour earlier
            10000,                                     // $100.00 entry
            barrier_result,
            1,    // positive label
            500,  // 5% return
            0.95, // high quality
            50,   // 50us processing
        );

        assert_eq!(label.label_value, 1);
        assert_eq!(label.return_bps, 500);
        assert!(label.is_profitable());
        assert!(label.meets_latency_target(80));
        assert!((label.return_as_ratio() - 0.05).abs() < 1e-10);
    }

    #[test]
    fn test_labeling_statistics() {
        let mut stats = LabelingStatistics::new();

        let barrier_result = BarrierResult::ProfitTarget;

        let label = EventLabel::new(
            1692000000_000_000_000 - 1800_000_000_000,
            10000,
            barrier_result,
            1,
            200, // 2% return
            0.9,
            40,
        );

        stats.update(&label);

        assert_eq!(stats.total_events, 1);
        assert_eq!(stats.positive_labels, 1);
        assert_eq!(stats.barrier_touch_counts[0], 1); // profit taking
        assert_eq!(stats.quality_distribution[2], 1); // high quality
    }
}
