//! Account information and position reconciliation.

use std::time::Duration;

use prost::Message;
use tracing::info;

use crate::dispatch::MessageDispatcher;
use crate::error::{CTraderError, Result};
use crate::proto::{self, ProtoMessage, ProtoOaOrder, ProtoOaPosition, ProtoOaTrader};

/// Account trader information.
#[derive(Debug, Clone)]
pub struct TraderInfo {
    /// Account balance in deposit currency (cents / base units).
    pub balance: i64,
    /// Deposit currency asset ID.
    pub deposit_asset_id: i64,
    /// Leverage in cents (e.g. 10000 = 1:100).
    pub leverage_in_cents: u32,
    /// Registration timestamp (Unix ms).
    pub registration_timestamp: i64,
    /// Raw trader proto for additional fields.
    pub raw: ProtoOaTrader,
}

/// Result of a position/order reconciliation.
#[derive(Debug, Clone)]
pub struct ReconcileResult {
    /// Open positions.
    pub positions: Vec<ProtoOaPosition>,
    /// Pending orders.
    pub orders: Vec<ProtoOaOrder>,
}

/// Query account trader information.
pub async fn get_trader_info(
    dispatcher: &MessageDispatcher,
    account_id: i64,
    timeout: Duration,
) -> Result<TraderInfo> {
    info!("querying trader info");

    let req = proto::ProtoOaTraderReq {
        payload_type: Some(proto::PT_TRADER_REQ as i32),
        ctid_trader_account_id: account_id,
    };

    let envelope = ProtoMessage {
        payload_type: proto::PT_TRADER_REQ,
        payload: Some(req.encode_to_vec()),
        client_msg_id: None,
    };

    let resp = dispatcher.send_request(envelope, timeout).await?;

    if resp.payload_type != proto::PT_TRADER_RES {
        return Err(CTraderError::ProtocolError(format!(
            "expected TraderRes ({}), got {}",
            proto::PT_TRADER_RES,
            resp.payload_type
        )));
    }

    let trader_res = proto::ProtoOaTraderRes::decode(
        resp.payload
            .as_deref()
            .ok_or_else(|| CTraderError::ProtocolError("empty TraderRes".into()))?,
    )?;

    // `trader` is a required field in the proto — prost gives it directly
    let trader = trader_res.trader;

    Ok(TraderInfo {
        balance: trader.balance,
        deposit_asset_id: trader.deposit_asset_id,
        leverage_in_cents: trader.leverage_in_cents.unwrap_or(0),
        registration_timestamp: trader.registration_timestamp.unwrap_or(0),
        raw: trader,
    })
}

/// Reconcile open positions and pending orders.
pub async fn reconcile(
    dispatcher: &MessageDispatcher,
    account_id: i64,
    timeout: Duration,
) -> Result<ReconcileResult> {
    info!("reconciling positions and orders");

    let req = proto::ProtoOaReconcileReq {
        payload_type: Some(proto::PT_RECONCILE_REQ as i32),
        ctid_trader_account_id: account_id,
        return_protection_orders: Some(false),
    };

    let envelope = ProtoMessage {
        payload_type: proto::PT_RECONCILE_REQ,
        payload: Some(req.encode_to_vec()),
        client_msg_id: None,
    };

    let resp = dispatcher.send_request(envelope, timeout).await?;

    if resp.payload_type != proto::PT_RECONCILE_RES {
        return Err(CTraderError::ProtocolError(format!(
            "expected ReconcileRes ({}), got {}",
            proto::PT_RECONCILE_RES,
            resp.payload_type
        )));
    }

    let reconcile_res = proto::ProtoOaReconcileRes::decode(
        resp.payload
            .as_deref()
            .ok_or_else(|| CTraderError::ProtocolError("empty ReconcileRes".into()))?,
    )?;

    info!(
        positions = reconcile_res.position.len(),
        orders = reconcile_res.order.len(),
        "reconcile complete"
    );

    Ok(ReconcileResult {
        positions: reconcile_res.position,
        orders: reconcile_res.order,
    })
}
