//! Order request builders for the cTrader Open API.
//!
//! Provides helper functions to construct `ProtoMessage` envelopes
//! for new order, cancel, amend, and close position requests.

use prost::Message;

use crate::proto::{self, ProtoMessage, ProtoOaTradeSide};

/// Build a new order request envelope.
#[allow(clippy::too_many_arguments)]
pub fn new_order(
    account_id: i64,
    symbol_id: i64,
    side: ProtoOaTradeSide,
    volume: i64,
    order_type: proto::ProtoOaOrderType,
    limit_price: Option<f64>,
    stop_price: Option<f64>,
    stop_loss: Option<f64>,
    take_profit: Option<f64>,
    comment: Option<String>,
) -> ProtoMessage {
    let req = proto::ProtoOaNewOrderReq {
        payload_type: Some(proto::PT_NEW_ORDER_REQ as i32),
        ctid_trader_account_id: account_id,
        symbol_id,
        order_type: order_type as i32,
        trade_side: side as i32,
        volume,
        limit_price,
        stop_price,
        time_in_force: None,
        expiration_timestamp: None,
        stop_loss,
        take_profit,
        comment,
        base_slippage_price: None,
        slippage_in_points: None,
        label: None,
        position_id: None,
        client_order_id: None,
        relative_stop_loss: None,
        relative_take_profit: None,
        guaranteed_stop_loss: None,
        trailing_stop_loss: None,
        stop_trigger_method: None,
    };

    ProtoMessage {
        payload_type: proto::PT_NEW_ORDER_REQ,
        payload: Some(req.encode_to_vec()),
        client_msg_id: None,
    }
}

/// Build a cancel order request envelope.
pub fn cancel_order(account_id: i64, order_id: i64) -> ProtoMessage {
    let req = proto::ProtoOaCancelOrderReq {
        payload_type: Some(proto::PT_CANCEL_ORDER_REQ as i32),
        ctid_trader_account_id: account_id,
        order_id,
    };

    ProtoMessage {
        payload_type: proto::PT_CANCEL_ORDER_REQ,
        payload: Some(req.encode_to_vec()),
        client_msg_id: None,
    }
}

/// Build an amend order request envelope.
#[allow(clippy::too_many_arguments)]
pub fn amend_order(
    account_id: i64,
    order_id: i64,
    volume: Option<i64>,
    limit_price: Option<f64>,
    stop_price: Option<f64>,
    stop_loss: Option<f64>,
    take_profit: Option<f64>,
) -> ProtoMessage {
    let req = proto::ProtoOaAmendOrderReq {
        payload_type: Some(proto::PT_AMEND_ORDER_REQ as i32),
        ctid_trader_account_id: account_id,
        order_id,
        volume,
        limit_price,
        stop_price,
        expiration_timestamp: None,
        stop_loss,
        take_profit,
        slippage_in_points: None,
        relative_stop_loss: None,
        relative_take_profit: None,
        guaranteed_stop_loss: None,
        trailing_stop_loss: None,
        stop_trigger_method: None,
    };

    ProtoMessage {
        payload_type: proto::PT_AMEND_ORDER_REQ,
        payload: Some(req.encode_to_vec()),
        client_msg_id: None,
    }
}

/// Build a close position request envelope.
pub fn close_position(account_id: i64, position_id: i64, volume: i64) -> ProtoMessage {
    let req = proto::ProtoOaClosePositionReq {
        payload_type: Some(proto::PT_CLOSE_POSITION_REQ as i32),
        ctid_trader_account_id: account_id,
        position_id,
        volume,
    };

    ProtoMessage {
        payload_type: proto::PT_CLOSE_POSITION_REQ,
        payload: Some(req.encode_to_vec()),
        client_msg_id: None,
    }
}

// ── Execution event parsing ──────────────────────────────────────

/// Parsed execution event info (avoids exposing raw proto types to consumers).
#[derive(Debug, Clone)]
pub struct ExecutionEventInfo {
    /// cTrader order ID.
    pub order_id: i64,
    /// Symbol ID.
    pub symbol_id: i64,
    /// Trade side as proto enum value (1=Buy, 2=Sell).
    pub trade_side: i32,
    /// Volume in cTrader units (cents of lot).
    pub volume: i64,
    /// Execution price (for filled orders).
    pub execution_price: Option<f64>,
    /// Executed volume in cents.
    pub executed_volume: Option<i64>,
    /// Linked position ID (if any).
    pub position_id: Option<i64>,
    /// Execution type as proto enum value.
    pub execution_type: i32,
    /// Open timestamp (Unix ms).
    pub timestamp: Option<i64>,
}

/// Try to parse an execution event from a raw `ProtoMessage`.
///
/// Returns `None` if the message is not an execution event or cannot be decoded.
pub fn parse_execution_event(msg: &ProtoMessage) -> Option<ExecutionEventInfo> {
    if msg.payload_type != proto::PT_EXECUTION_EVENT {
        return None;
    }
    let payload = msg.payload.as_deref()?;
    let event = proto::ProtoOaExecutionEvent::decode(payload).ok()?;
    let order = event.order?;
    Some(ExecutionEventInfo {
        order_id: order.order_id,
        symbol_id: order.trade_data.symbol_id,
        trade_side: order.trade_data.trade_side,
        volume: order.trade_data.volume,
        execution_price: order.execution_price,
        executed_volume: order.executed_volume,
        position_id: event.position.map(|p| p.position_id),
        execution_type: event.execution_type,
        timestamp: order.trade_data.open_timestamp,
    })
}

/// Extract the order ID from an execution response message.
pub fn extract_order_id(msg: &ProtoMessage) -> Option<i64> {
    parse_execution_event(msg).map(|e| e.order_id)
}

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

    #[test]
    fn new_order_builds_valid_envelope() {
        let msg = new_order(
            12345,
            1,
            ProtoOaTradeSide::Buy,
            100_000,
            proto::ProtoOaOrderType::Market,
            None,
            None,
            None,
            None,
            Some("test order".into()),
        );

        assert_eq!(msg.payload_type, proto::PT_NEW_ORDER_REQ);
        assert!(msg.payload.is_some());

        let decoded = proto::ProtoOaNewOrderReq::decode(
            msg.payload.as_deref().expect("payload"),
        )
        .expect("decode");
        assert_eq!(decoded.ctid_trader_account_id, 12345);
        assert_eq!(decoded.symbol_id, 1);
        assert_eq!(decoded.trade_side, ProtoOaTradeSide::Buy as i32);
        assert_eq!(decoded.volume, 100_000);
        assert_eq!(
            decoded.order_type,
            proto::ProtoOaOrderType::Market as i32
        );
        assert_eq!(decoded.comment.as_deref(), Some("test order"));
    }

    #[test]
    fn cancel_order_envelope() {
        let msg = cancel_order(12345, 999);
        assert_eq!(msg.payload_type, proto::PT_CANCEL_ORDER_REQ);

        let decoded = proto::ProtoOaCancelOrderReq::decode(
            msg.payload.as_deref().expect("payload"),
        )
        .expect("decode");
        assert_eq!(decoded.ctid_trader_account_id, 12345);
        assert_eq!(decoded.order_id, 999);
    }

    #[test]
    fn close_position_envelope() {
        let msg = close_position(12345, 777, 50_000);
        assert_eq!(msg.payload_type, proto::PT_CLOSE_POSITION_REQ);

        let decoded = proto::ProtoOaClosePositionReq::decode(
            msg.payload.as_deref().expect("payload"),
        )
        .expect("decode");
        assert_eq!(decoded.ctid_trader_account_id, 12345);
        assert_eq!(decoded.position_id, 777);
        assert_eq!(decoded.volume, 50_000);
    }
}
