///
///
///
///
///
///
///
///
///
///
///
///
///
///
use prost::Message;
use tokio::sync::oneshot;

use crate::payload;
use crate::proto::common::*;
use crate::{client::Client, error::Error};

// ── Internal helper ───────────────────────────────────────────────────────────

impl Client {
    /// Send a `ProtoOANewOrderReq` without a `clientMsgId` and await the
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    /// resulting `ProtoOAExecutionEvent` (or `ProtoOAOrderErrorEvent`) via the
    ///
    ///
    ///
    /// pending-orders queue.
    async fn send_order(&self, req: ProtoOaNewOrderReq) -> Result<ProtoOaExecutionEvent, Error> {
        // Register waiter BEFORE sending to avoid a race
        let (_tx, rx) = oneshot::channel::<Result<ProtoOaExecutionEvent, crate::error::Error>>();
        // self.pending_orders.lock().await.push(tx);

        // Encode inner message
        let mut payload_bytes = Vec::new();
        req.encode(&mut payload_bytes)?;

        // Envelope — intentionally NO clientMsgId so the server sends an
        // unsolicited ExecutionEvent back
        let envelope = ProtoMessage {
            payload_type: payload::OA_NEW_ORDER_REQ as u32,
            payload: Some(payload_bytes),
            client_msg_id: None,
        };

        let mut frame = Vec::new();
        envelope.encode(&mut frame)?;
        self.transport.send(&frame).await?;

        tokio::time::timeout(self.config.deadline, rx)
            .await
            .map_err(|_| Error::Timeout)?
            .map_err(|_| Error::Disconnected)?
    }

    // ── Market orders ─────────────────────────────────────────────────────────

    /// Place a market order (immediate fill at best available price).
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    /// `volume` is in units of 0.01 lots: 100 = 0.01 lot, 100_000 = 1 lot.
    pub async fn new_market_order(
        &self,
        ctid_trader_account_id: i64,
        symbol_id: i64,
        trade_side: ProtoOaTradeSide,
        volume: i64,
    ) -> Result<ProtoOaExecutionEvent, Error> {
        self.send_order(ProtoOaNewOrderReq {
            ctid_trader_account_id,
            symbol_id,
            trade_side: trade_side as i32,
            volume,
            order_type: ProtoOaOrderType::Market as i32,
            payload_type: Some(payload::OA_NEW_ORDER_REQ as i32),
            ..Default::default()
        })
        .await
    }

    /// Place a market order with relative SL/TP (distance from fill price).
    ///
    ///
    ///
    ///
    /// `relative_stop_loss` and `relative_take_profit` are in 1/100000 of a
    /// price unit.  1 pip on a 5-decimal pair = 10 points = 1000 in protocol.
    ///
    /// For a BUY:   SL = fillPrice − relSL,  TP = fillPrice + relTP
    /// For a SELL:  SL = fillPrice + relSL,  TP = fillPrice − relTP
    ///
    ///
    ///
    ///
    /// Helper: `pips_to_points(n)` converts pip count → protocol value.
    pub async fn new_market_order_with_sltp(
        &self,
        ctid_trader_account_id: i64,
        symbol_id: i64,
        trade_side: ProtoOaTradeSide,
        volume: i64,
        relative_stop_loss: i64,
        relative_take_profit: i64,
    ) -> Result<ProtoOaExecutionEvent, Error> {
        self.send_order(ProtoOaNewOrderReq {
            ctid_trader_account_id,
            symbol_id,
            trade_side: trade_side as i32,
            volume,
            order_type: ProtoOaOrderType::Market as i32,
            relative_stop_loss: Some(relative_stop_loss),
            relative_take_profit: Some(relative_take_profit),
            payload_type: Some(payload::OA_NEW_ORDER_REQ as i32),
            ..Default::default()
        })
        .await
    }

    /// Market order with trailing stop loss (relative distance).
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    pub async fn new_market_order_with_trailing_sl(
        &self,
        ctid_trader_account_id: i64,
        symbol_id: i64,
        trade_side: ProtoOaTradeSide,
        volume: i64,
        relative_stop_loss: i64,
    ) -> Result<ProtoOaExecutionEvent, Error> {
        self.send_order(ProtoOaNewOrderReq {
            ctid_trader_account_id,
            symbol_id,
            trade_side: trade_side as i32,
            volume,
            order_type: ProtoOaOrderType::Market as i32,
            relative_stop_loss: Some(relative_stop_loss),
            trailing_stop_loss: Some(true),
            payload_type: Some(payload::OA_NEW_ORDER_REQ as i32),
            ..Default::default()
        })
        .await
    }

    /// Market range order — fills within a slippage band around `base_price`.
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    /// `slippage_in_points` is the maximum allowed deviation in points.
    pub async fn new_market_range_order(
        &self,
        ctid_trader_account_id: i64,
        symbol_id: i64,
        trade_side: ProtoOaTradeSide,
        volume: i64,
        base_slippage_price: f64,
        slippage_in_points: i32,
    ) -> Result<ProtoOaExecutionEvent, Error> {
        self.send_order(ProtoOaNewOrderReq {
            volume,
            symbol_id,
            ctid_trader_account_id,
            trade_side: trade_side as i32,
            slippage_in_points: Some(slippage_in_points),
            base_slippage_price: Some(base_slippage_price),
            order_type: ProtoOaOrderType::MarketRange as i32,
            payload_type: Some(payload::OA_NEW_ORDER_REQ as i32),
            ..Default::default()
        })
        .await
    }

    // ── Pending orders ────────────────────────────────────────────────────────

    /// Place a limit order (fill when price reaches `limit_price` or better).
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    /// `stop_loss` and `take_profit` are absolute price levels.
    /// Leave as `None` to omit them.
    pub async fn new_limit_order(
        &self,
        ctid_trader_account_id: i64,
        symbol_id: i64,
        trade_side: ProtoOaTradeSide,
        volume: i64,
        limit_price: f64,
        stop_loss: Option<f64>,
        take_profit: Option<f64>,
    ) -> Result<ProtoOaExecutionEvent, Error> {
        self.send_order(ProtoOaNewOrderReq {
            volume,
            symbol_id,
            stop_loss,
            take_profit,
            ctid_trader_account_id,
            limit_price: Some(limit_price),
            trade_side: trade_side as i32,
            order_type: ProtoOaOrderType::Limit as i32,
            payload_type: Some(payload::OA_NEW_ORDER_REQ as i32),
            ..Default::default()
        })
        .await
    }

    /// Place a stop order (fill when price breaks through `stop_price`).
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    /// `stop_loss` and `take_profit` are absolute price levels.
    pub async fn new_stop_order(
        &self,
        ctid_trader_account_id: i64,
        symbol_id: i64,
        trade_side: ProtoOaTradeSide,
        volume: i64,
        stop_price: f64,
        stop_loss: Option<f64>,
        take_profit: Option<f64>,
    ) -> Result<ProtoOaExecutionEvent, Error> {
        self.send_order(ProtoOaNewOrderReq {
            volume,
            symbol_id,
            stop_loss,
            take_profit,
            ctid_trader_account_id,
            stop_price: Some(stop_price),
            trade_side: trade_side as i32,
            order_type: ProtoOaOrderType::Stop as i32,
            payload_type: Some(payload::OA_NEW_ORDER_REQ as i32),
            ..Default::default()
        })
        .await
    }

    /// Place a stop-limit order (pending stop that becomes a limit on trigger).
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    /// `stop_price`       — price level that triggers the order.
    /// `slippage_in_points` — max deviation from stop price for the limit fill.
    pub async fn new_stop_limit_order(
        &self,
        ctid_trader_account_id: i64,
        symbol_id: i64,
        trade_side: ProtoOaTradeSide,
        volume: i64,
        stop_price: f64,
        slippage_in_points: i32,
        stop_loss: Option<f64>,
        take_profit: Option<f64>,
    ) -> Result<ProtoOaExecutionEvent, Error> {
        self.send_order(ProtoOaNewOrderReq {
            volume,
            symbol_id,
            stop_loss,
            take_profit,
            ctid_trader_account_id,
            stop_price: Some(stop_price),
            trade_side: trade_side as i32,
            slippage_in_points: Some(slippage_in_points),
            order_type: ProtoOaOrderType::StopLimit as i32,
            payload_type: Some(payload::OA_NEW_ORDER_REQ as i32),
            ..Default::default()
        })
        .await
    }

    // ── Order management ──────────────────────────────────────────────────────

    /// Cancel an existing pending order.
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    pub async fn cancel_order(
        &self,
        ctid_trader_account_id: i64,
        order_id: i64,
    ) -> Result<ProtoOaExecutionEvent, Error> {
        let req = ProtoOaCancelOrderReq {
            order_id,
            ctid_trader_account_id,
            payload_type: Some(payload::OA_CANCEL_ORDER_REQ as i32),
        };

        self.command(
            payload::OA_CANCEL_ORDER_REQ,
            req,
            payload::OA_EXECUTION_EVENT,
        )
        .await
    }

    /// Amend an existing pending order.
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    /// Only supply the fields you want to change — all are optional except
    /// `ctid_trader_account_id` and `order_id`.
    pub async fn amend_order(
        &self,
        ctid_trader_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>,
        trailing_stop_loss: Option<bool>,
        expiration_timestamp: Option<i64>,
    ) -> Result<ProtoOaExecutionEvent, Error> {
        let req = ProtoOaAmendOrderReq {
            order_id,
            volume,
            limit_price,
            stop_price,
            stop_loss,
            take_profit,
            trailing_stop_loss,
            expiration_timestamp,
            ctid_trader_account_id,
            payload_type: Some(payload::OA_AMEND_ORDER_REQ as i32),
            ..Default::default()
        };
        self.command(
            payload::OA_AMEND_ORDER_REQ,
            req,
            payload::OA_EXECUTION_EVENT,
        )
        .await
    }

    /// Get open positions and pending orders (current state snapshot).
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    /// Set `return_protection_orders = true` to receive SL/TP as separate
    /// orders; otherwise they appear as fields on the position.
    pub async fn reconcile(
        &self,
        ctid_trader_account_id: i64,
        return_protection_orders: bool,
    ) -> Result<ProtoOaReconcileRes, Error> {
        let req = ProtoOaReconcileReq {
            ctid_trader_account_id,
            payload_type: Some(payload::OA_RECONCILE_REQ as i32),
            return_protection_orders: Some(return_protection_orders),
        };
        self.command(payload::OA_RECONCILE_REQ, req, payload::OA_RECONCILE_RES)
            .await
    }

    /// List orders filtered by time range.
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    pub async fn order_list(
        &self,
        ctid_trader_account_id: i64,
        from_timestamp: Option<i64>,
        to_timestamp: Option<i64>,
    ) -> Result<ProtoOaOrderListRes, Error> {
        let req = ProtoOaOrderListReq {
            to_timestamp,
            from_timestamp,
            ctid_trader_account_id,
            payload_type: Some(payload::OA_ORDER_LIST_REQ as i32),
        };
        self.command(payload::OA_ORDER_LIST_REQ, req, payload::OA_ORDER_LIST_RES)
            .await
    }

    /// Get full details for a single order by ID.
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    pub async fn order_details(
        &self,
        ctid_trader_account_id: i64,
        order_id: i64,
    ) -> Result<ProtoOaOrderDetailsRes, Error> {
        let req = ProtoOaOrderDetailsReq {
            order_id,
            ctid_trader_account_id,
            payload_type: Some(payload::OA_ORDER_DETAILS_REQ as i32),
        };
        self.command(
            payload::OA_ORDER_DETAILS_REQ,
            req,
            payload::OA_ORDER_DETAILS_RES,
        )
        .await
    }

    /// List orders associated with a specific position.
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    ///
    pub async fn get_order_list_by_position(
        &self,
        ctid_trader_account_id: i64,
        position_id: i64,
        from_timestamp: Option<i64>,
        to_timestamp: Option<i64>,
    ) -> Result<ProtoOaOrderListByPositionIdRes, Error> {
        let req = ProtoOaOrderListByPositionIdReq {
            position_id,
            to_timestamp,
            from_timestamp,
            ctid_trader_account_id,
            payload_type: Some(payload::OA_ORDER_LIST_BY_POSITION_ID_REQ as i32),
        };
        self.command(
            payload::OA_ORDER_LIST_BY_POSITION_ID_REQ,
            req,
            payload::OA_ORDER_LIST_BY_POSITION_ID_RES,
        )
        .await
    }
}

#[cfg(test)]
mod tests {

    #[tokio::test]
    async fn test() {}
}
