//! Pipeline stage trait defining the processing contract.

use super::messages::PipelineMessage;
use std::fmt;

/// Result of a pipeline stage processing a message.
pub type StageResult = Result<PipelineMessage, StageError>;

/// Error from a pipeline stage.
#[derive(Debug, Clone)]
pub struct StageError {
    /// Name of the stage that produced the error.
    pub stage_name: String,
    /// Human-readable error description.
    pub message: String,
}

impl fmt::Display for StageError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "[{}] {}", self.stage_name, self.message)
    }
}

impl std::error::Error for StageError {}

/// Trait for a pipeline processing stage.
///
/// Each stage receives a message, processes it, and returns the next message.
/// Stages can reject messages by returning `PipelineMessage::Rejected`.
pub trait PipelineStage: Send + Sync + fmt::Debug {
    /// Process a pipeline message and produce the next message.
    fn process(&self, msg: PipelineMessage) -> StageResult;

    /// Human-readable name of this stage.
    fn name(&self) -> &str;
}

#[cfg(test)]
mod tests {
    use super::*;
    use super::super::messages::*;

    #[derive(Debug)]
    struct PassthroughStage;

    impl PipelineStage for PassthroughStage {
        fn process(&self, msg: PipelineMessage) -> StageResult {
            Ok(msg)
        }
        fn name(&self) -> &str {
            "passthrough"
        }
    }

    #[test]
    fn test_passthrough_stage() {
        let stage = PassthroughStage;
        let msg = PipelineMessage::Prediction(PredictionSignal {
            symbol: "EURUSD".to_string(),
            direction: SignalDirection::Long,
            confidence: 0.8,
            model_id: "dqn_v1".to_string(),
        });
        let result = stage.process(msg);
        assert!(result.is_ok());
    }

    #[test]
    fn test_stage_error_display() {
        let err = StageError {
            stage_name: "risk_gate".to_string(),
            message: "drawdown exceeded".to_string(),
        };
        assert_eq!(format!("{err}"), "[risk_gate] drawdown exceeded");
    }

    #[test]
    fn test_rejection_message() {
        let msg = PipelineMessage::Rejected {
            reason: "kill switch active".to_string(),
            original_symbol: "EURUSD".to_string(),
        };
        match msg {
            PipelineMessage::Rejected { reason, .. } => {
                assert_eq!(reason, "kill switch active");
            }
            _ => {
                // Test should only reach here if enum variant doesn't match
                panic!("Expected Rejected variant");
            }
        }
    }
}
