pub use crate::common::action::{ExposureLevel, FactoredAction, OrderType, Urgency};

/// Returns action mask where true=valid, false=invalid based on position limits.
///
/// Prevents invalid exposure actions that would violate position limits.
/// DQN outputs 5 exposure actions (Short100, Short50, Flat, Long50, Long100).
///
/// # Arguments
/// * `_current_position` - Current portfolio position (unused - actions use absolute targets)
/// * `max_position` - Maximum allowed position magnitude (typically 2.0)
///
/// # Returns
/// Boolean mask of length 5 where:
/// - `true` = exposure action is valid (does not violate position limits)
/// - `false` = exposure action is invalid (would exceed max_position)
pub fn get_valid_action_mask(_current_position: f64, max_position: f64) -> Vec<bool> {
    let exposures = [-1.0_f64, -0.5, 0.0, 0.5, 1.0]; // Short100..Long100
    exposures
        .iter()
        .map(|&exp| exp.abs() <= max_position)
        .collect()
}

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

    #[test]
    fn test_exposure_enum_values() {
        assert_eq!(ExposureLevel::Short100 as usize, 0);
        assert_eq!(ExposureLevel::Short75 as usize, 1);
        assert_eq!(ExposureLevel::Short50 as usize, 2);
        assert_eq!(ExposureLevel::Short25 as usize, 3);
        assert_eq!(ExposureLevel::Flat as usize, 4);
        assert_eq!(ExposureLevel::Long25 as usize, 5);
        assert_eq!(ExposureLevel::Long50 as usize, 6);
        assert_eq!(ExposureLevel::Long75 as usize, 7);
        assert_eq!(ExposureLevel::Long100 as usize, 8);
    }

    #[test]
    fn test_order_type_enum_values() {
        assert_eq!(OrderType::Market as usize, 0);
        assert_eq!(OrderType::LimitMaker as usize, 1);
        assert_eq!(OrderType::IoC as usize, 2);
    }

    #[test]
    fn test_urgency_enum_values() {
        assert_eq!(Urgency::Patient as usize, 0);
        assert_eq!(Urgency::Normal as usize, 1);
        assert_eq!(Urgency::Aggressive as usize, 2);
    }

    #[test]
    fn test_action_from_index_bidirectional() {
        // Test all 45 actions round-trip
        for idx in 0..45 {
            let action = FactoredAction::from_index(idx).unwrap();
            assert_eq!(
                action.to_index(),
                idx,
                "Round-trip failed for index {}",
                idx
            );
        }
    }

    #[test]
    fn test_action_from_index_bounds() {
        // Test out of bounds indices
        assert!(FactoredAction::from_index(81).is_err());
        assert!(FactoredAction::from_index(100).is_err());
        assert!(FactoredAction::from_index(usize::MAX).is_err());
    }

    #[test]
    fn test_target_exposure_values() {
        assert_eq!(ExposureLevel::Short100.target_exposure(), -1.0);
        assert_eq!(ExposureLevel::Short50.target_exposure(), -0.5);
        assert_eq!(ExposureLevel::Flat.target_exposure(), 0.0);
        assert_eq!(ExposureLevel::Long50.target_exposure(), 0.5);
        assert_eq!(ExposureLevel::Long100.target_exposure(), 1.0);
    }

    #[test]
    fn test_transaction_costs() {
        // Wave 2.5 Calibration: Updated expected values
        assert_eq!(OrderType::Market.transaction_cost(), 0.0015);
        assert_eq!(OrderType::LimitMaker.transaction_cost(), 0.0005);
        assert_eq!(OrderType::IoC.transaction_cost(), 0.0010);
    }

    #[test]
    fn test_urgency_weights() {
        assert_eq!(Urgency::Patient.urgency_weight(), 0.5);
        assert_eq!(Urgency::Normal.urgency_weight(), 1.0);
        assert_eq!(Urgency::Aggressive.urgency_weight(), 1.5);
    }

    #[test]
    fn test_action_equality() {
        let action1 = FactoredAction::new(
            ExposureLevel::Long100,
            OrderType::Market,
            Urgency::Aggressive,
        );
        let action2 = FactoredAction::new(
            ExposureLevel::Long100,
            OrderType::Market,
            Urgency::Aggressive,
        );
        let action3 = FactoredAction::new(ExposureLevel::Flat, OrderType::Market, Urgency::Normal);

        assert_eq!(action1, action2);
        assert_ne!(action1, action3);
    }

    #[test]
    fn test_action_debug() {
        let action = FactoredAction::new(
            ExposureLevel::Long50,
            OrderType::LimitMaker,
            Urgency::Patient,
        );
        let debug_str = format!("{:?}", action);
        assert!(debug_str.contains("Long50"));
        assert!(debug_str.contains("LimitMaker"));
        assert!(debug_str.contains("Patient"));
    }

    #[test]
    fn test_action_clone() {
        let action1 = FactoredAction::new(ExposureLevel::Short50, OrderType::IoC, Urgency::Normal);
        let action2 = action1.clone();
        assert_eq!(action1, action2);
    }

    #[test]
    fn test_index_bijection() {
        // Verify all 45 indices map to unique actions
        use std::collections::HashSet;
        let mut actions = HashSet::new();

        for idx in 0..45 {
            let action = FactoredAction::from_index(idx).unwrap();
            assert!(actions.insert(action), "Duplicate action for index {}", idx);
        }

        assert_eq!(actions.len(), 45);
    }

    #[test]
    fn test_flat_market_normal_action() {
        // Test common neutral action
        let action = FactoredAction::new(ExposureLevel::Flat, OrderType::Market, Urgency::Normal);

        // Index = 4 * 9 + 0 * 3 + 1 = 36 + 0 + 1 = 37
        assert_eq!(action.to_index(), 37);
        assert_eq!(action.target_exposure(), 0.0);
        assert_eq!(action.transaction_cost(), 0.0015); // Wave 2.5 calibration
        assert_eq!(action.urgency_weight(), 1.0);

        // Round-trip
        let reconstructed = FactoredAction::from_index(37).unwrap();
        assert_eq!(action, reconstructed);
    }

    #[test]
    fn test_extreme_actions() {
        // Test Short100 aggressive market (index 0 * 9 + 0 * 3 + 2 = 2)
        let short_extreme = FactoredAction::new(
            ExposureLevel::Short100,
            OrderType::Market,
            Urgency::Aggressive,
        );
        assert_eq!(short_extreme.to_index(), 2);
        assert_eq!(short_extreme.target_exposure(), -1.0);
        assert_eq!(short_extreme.transaction_cost(), 0.0015); // Wave 2.5 calibration
        assert_eq!(short_extreme.urgency_weight(), 1.5);

        // Test Long100 aggressive market (index 8 * 9 + 0 * 3 + 2 = 74)
        let long_extreme = FactoredAction::new(
            ExposureLevel::Long100,
            OrderType::Market,
            Urgency::Aggressive,
        );
        assert_eq!(long_extreme.to_index(), 74);
        assert_eq!(long_extreme.target_exposure(), 1.0);
        assert_eq!(long_extreme.transaction_cost(), 0.0015); // Wave 2.5 calibration
        assert_eq!(long_extreme.urgency_weight(), 1.5);
    }

    #[test]
    fn test_serialization() {
        let action = FactoredAction::new(
            ExposureLevel::Long50,
            OrderType::LimitMaker,
            Urgency::Patient,
        );

        // Serialize to JSON
        let json = serde_json::to_string(&action).unwrap();

        // Deserialize back
        let deserialized: FactoredAction = serde_json::from_str(&json).unwrap();

        assert_eq!(action, deserialized);
    }

    #[test]
    fn test_action_masking_all_valid_at_standard_limit() {
        // With max_position=2.0, all 5 exposure actions should be valid
        let mask = get_valid_action_mask(0.0, 2.0);
        assert_eq!(mask.len(), 5);
        assert!(
            mask.iter().all(|&v| v),
            "All 5 exposure actions should be valid at max_position=2.0"
        );
    }

    #[test]
    fn test_action_masking_at_limit_1() {
        // With max_position=1.0, all actions valid (max exposure = 1.0)
        let mask = get_valid_action_mask(0.0, 1.0);
        assert_eq!(mask.len(), 5);
        assert!(mask.iter().all(|&v| v), "All valid at max_position=1.0");
    }

    #[test]
    fn test_action_masking_restrictive_limit() {
        // With max_position=0.6, Short100/Long100 masked (exposure ±1.0 > 0.6)
        let mask = get_valid_action_mask(0.0, 0.6);
        assert_eq!(mask.len(), 5);
        assert!(!mask[0], "Short100 should be INVALID at max_position=0.6");
        assert!(mask[1], "Short50 should be valid at max_position=0.6");
        assert!(mask[2], "Flat should be valid at max_position=0.6");
        assert!(mask[3], "Long50 should be valid at max_position=0.6");
        assert!(!mask[4], "Long100 should be INVALID at max_position=0.6");
    }

    #[test]
    fn test_action_masking_flat_always_valid() {
        for pos in [-1.5, -1.0, 0.0, 1.0, 1.5] {
            let mask = get_valid_action_mask(pos, 0.1);
            assert!(mask[2], "Flat (exposure=0) should always be valid");
        }
    }
}
