use std::num::NonZeroU64;

use async_trait::async_trait;
use chrono::{DateTime, Utc};
use uuid::Uuid;

use crate::shared::{
    models::{
        leverage::Leverage,
        price::Price,
        trade::{TradeExecution, TradeSide, TradeSize, TradeStatus},
    },
    rest::error::Result,
};

use super::models::{price_history::PriceEntry, ticker::Ticker, trade::Trade, user::User};

/// Methods for interacting with [LNM's v2 API]'s REST Futures endpoints.
///
/// This trait is sealed and not meant to be implemented outside of `lnm-sdk`.
///
/// [LNM's v2 API]: https://docs.lnmarkets.com/api/#overview
#[async_trait]
pub trait FuturesRepository: crate::sealed::Sealed + Send + Sync {
    /// Fetch the user's trades by [`TradeStatus`].
    ///
    /// **Required permission**: `Retrieve open and running trades` or `Retrieve closed trades`.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # #[allow(deprecated)]
    /// # async fn example(rest: lnm_sdk::api_v2::RestClient) -> Result<(), Box<dyn std::error::Error>> {
    /// use lnm_sdk::api_v2::models::{Trade, TradeStatus};
    ///
    /// let open_trades: Vec<Trade> = rest
    ///     .futures
    ///     .get_trades(TradeStatus::Open, None, None, None)
    ///     .await?;
    ///
    /// let running_trades: Vec<Trade> = rest
    ///     .futures
    ///     .get_trades(TradeStatus::Running, None, None, None)
    ///     .await?;
    ///
    /// let closed_trades: Vec<Trade> = rest
    ///     .futures
    ///     .get_trades(TradeStatus::Closed, None, None, None)
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    async fn get_trades(
        &self,
        status: TradeStatus,
        from: Option<DateTime<Utc>>,
        to: Option<DateTime<Utc>>,
        limit: Option<usize>,
    ) -> Result<Vec<Trade>>;

    /// Fetch the user's open trades.
    ///
    /// **Required permission**: `Retrieve open and running trades`
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # #[allow(deprecated)]
    /// # async fn example(rest: lnm_sdk::api_v2::RestClient) -> Result<(), Box<dyn std::error::Error>> {
    /// use lnm_sdk::api_v2::models::Trade;
    ///
    /// let open_trades: Vec<Trade> = rest
    ///     .futures
    ///     .get_trades_open(None, None, None)
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    async fn get_trades_open(
        &self,
        from: Option<DateTime<Utc>>,
        to: Option<DateTime<Utc>>,
        limit: Option<usize>,
    ) -> Result<Vec<Trade>>;

    /// Fetch the user's running trades.
    ///
    /// **Required permission**: `Retrieve open and running trades`
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # #[allow(deprecated)]
    /// # async fn example(rest: lnm_sdk::api_v2::RestClient) -> Result<(), Box<dyn std::error::Error>> {
    /// use lnm_sdk::api_v2::models::Trade;
    ///
    /// let running_trades: Vec<Trade> = rest
    ///     .futures
    ///     .get_trades_running(None, None, None)
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    async fn get_trades_running(
        &self,
        from: Option<DateTime<Utc>>,
        to: Option<DateTime<Utc>>,
        limit: Option<usize>,
    ) -> Result<Vec<Trade>>;

    /// Fetch the user's closed trades.
    ///
    /// **Required permission**: `Retrieve closed trades`
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # #[allow(deprecated)]
    /// # async fn example(rest: lnm_sdk::api_v2::RestClient) -> Result<(), Box<dyn std::error::Error>> {
    /// use lnm_sdk::api_v2::models::Trade;
    ///
    /// let closed_trades: Vec<Trade> = rest
    ///     .futures
    ///     .get_trades_closed(None, None, None)
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    async fn get_trades_closed(
        &self,
        from: Option<DateTime<Utc>>,
        to: Option<DateTime<Utc>>,
        limit: Option<usize>,
    ) -> Result<Vec<Trade>>;

    /// Retrieve price history between two given timestamps. Limited to 1000 entries.
    ///
    /// **Public endpoint** - No authentication required.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # #[allow(deprecated)]
    /// # async fn example(rest: lnm_sdk::api_v2::RestClient) -> Result<(), Box<dyn std::error::Error>> {
    /// use lnm_sdk::api_v2::models::PriceEntry;
    ///
    /// let price_history: Vec<PriceEntry> = rest
    ///     .futures
    ///     .price_history(None, None, None)
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    async fn price_history(
        &self,
        from: Option<DateTime<Utc>>,
        to: Option<DateTime<Utc>>,
        limit: Option<usize>,
    ) -> Result<Vec<PriceEntry>>;

    /// Create a new trade.
    ///
    /// **Required permission**: `Create new trades`
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # #[allow(deprecated)]
    /// # async fn example(rest: lnm_sdk::api_v2::RestClient) -> Result<(), Box<dyn std::error::Error>> {
    /// use lnm_sdk::api_v2::models::{
    ///     Leverage, Margin, Price, Quantity, Trade, TradeExecution, TradeSide, TradeSize,
    /// };
    ///
    /// // Create long market order with 10,000 sats of margin and no leverage,
    /// // stoploss or takeprofit.
    /// let trade: Trade = rest
    ///     .futures
    ///     .create_new_trade(
    ///         TradeSide::Buy,
    ///         TradeSize::from(Margin::try_from(10_000)?),
    ///         Leverage::try_from(1)?,
    ///         TradeExecution::Market,
    ///         None,
    ///         None,
    ///     )
    ///     .await?;
    ///
    /// // Create long limit order at the price of 120,000 [USD/BTC] with 10 USD
    /// // of quantity and 2x leverage.
    /// // Stoploss at the price of 110,000 [USD/BTC] and takeprofit at the
    /// // price of 130,000 [USD/BTC].
    /// let trade: Trade = rest
    ///     .futures
    ///     .create_new_trade(
    ///         TradeSide::Buy,
    ///         TradeSize::from(Quantity::try_from(10)?),
    ///         Leverage::try_from(2)?,
    ///         TradeExecution::Limit(Price::try_from(120_000)?),
    ///         Some(Price::try_from(110_000)?),
    ///         Some(Price::try_from(130_000)?),
    ///     )
    ///     .await?;
    ///
    /// // Create short limit order at the price of 130,000 [USD/BTC] with 10 USD
    /// // of quantity and 3x leverage.
    /// // Stoploss at the price of 140,000 [USD/BTC] and takeprofit at the
    /// // price of 120,000 [USD/BTC].
    /// let trade: Trade = rest
    ///     .futures
    ///     .create_new_trade(
    ///         TradeSide::Sell,
    ///         TradeSize::from(Quantity::try_from(10)?),
    ///         Leverage::try_from(3)?,
    ///         TradeExecution::Limit(Price::try_from(130_000)?),
    ///         Some(Price::try_from(140_000)?),
    ///         Some(Price::try_from(120_000)?),
    ///     )
    ///     .await?;
    /// # Ok(())
    /// # }
    /// ```
    async fn create_new_trade(
        &self,
        side: TradeSide,
        size: TradeSize,
        leverage: Leverage,
        execution: TradeExecution,
        stoploss: Option<Price>,
        takeprofit: Option<Price>,
    ) -> Result<Trade>;

    /// Get a trade by id.
    ///
    /// **Required permission**: `Retrieve open and running trades` or `Retrieve closed trades`
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # #[allow(deprecated)]
    /// # async fn example(rest: lnm_sdk::api_v2::RestClient) -> Result<(), Box<dyn std::error::Error>> {
    /// use lnm_sdk::api_v2::models::{Leverage, Margin, Trade, TradeExecution, TradeSide, TradeSize};
    ///
    /// let running_trade: Trade = rest
    ///     .futures
    ///     .create_new_trade(
    ///         TradeSide::Buy,
    ///         TradeSize::from(Margin::try_from(10_000)?),
    ///         Leverage::try_from(1)?,
    ///         TradeExecution::Market,
    ///         None,
    ///         None,
    ///     )
    ///     .await?;
    ///
    /// let same_trade: Trade = rest
    ///     .futures
    ///     .get_trade(running_trade.id())
    ///     .await?;
    ///
    /// assert_eq!(running_trade.id(), same_trade.id());
    /// # Ok(())
    /// # }
    /// ```
    async fn get_trade(&self, id: Uuid) -> Result<Trade>;

    /// Cancel an open trade.
    ///
    /// **Required permission**: `Close and cancel trades`
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # #[allow(deprecated)]
    /// # async fn example(rest: lnm_sdk::api_v2::RestClient) -> Result<(), Box<dyn std::error::Error>> {
    /// use lnm_sdk::api_v2::models::{
    ///     Leverage, Price, Quantity, Trade, TradeExecution, TradeSide, TradeSize,
    /// };
    ///
    /// let open_trade: Trade = rest
    ///     .futures
    ///     .create_new_trade(
    ///         TradeSide::Buy,
    ///         TradeSize::from(Quantity::try_from(10)?),
    ///         Leverage::try_from(1)?,
    ///         TradeExecution::Limit(Price::try_from(10_000)?),
    ///         None,
    ///         None,
    ///     )
    ///     .await?;
    ///
    /// // Assuming trade is still open
    ///
    /// let cancelled_trade: Trade = rest
    ///     .futures
    ///     .cancel_trade(open_trade.id())
    ///     .await?;
    ///
    /// assert_eq!(cancelled_trade.id(), open_trade.id());
    /// # Ok(())
    /// # }
    /// ```
    async fn cancel_trade(&self, id: Uuid) -> Result<Trade>;

    /// Cancel all open trades.
    ///
    /// **Required permission**: `Close and cancel trades`
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # #[allow(deprecated)]
    /// # async fn example(rest: lnm_sdk::api_v2::RestClient) -> Result<(), Box<dyn std::error::Error>> {
    /// use lnm_sdk::api_v2::models::Trade;
    ///
    /// let cancelled_trades: Vec<Trade> = rest.futures.cancel_all_trades().await?;
    /// # Ok(())
    /// # }
    /// ```
    async fn cancel_all_trades(&self) -> Result<Vec<Trade>>;

    /// Close a running trade.
    ///
    /// **Required permission**: `Close and cancel trades`
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # #[allow(deprecated)]
    /// # async fn example(rest: lnm_sdk::api_v2::RestClient) -> Result<(), Box<dyn std::error::Error>> {
    /// use lnm_sdk::api_v2::models::{Leverage, Margin, Trade, TradeExecution, TradeSide, TradeSize};
    ///
    /// let running_trade: Trade = rest
    ///     .futures
    ///     .create_new_trade(
    ///         TradeSide::Buy,
    ///         TradeSize::from(Margin::try_from(10_000)?),
    ///         Leverage::try_from(1)?,
    ///         TradeExecution::Market,
    ///         None,
    ///         None,
    ///     )
    ///     .await?;
    ///
    /// let closed_trade: Trade = rest
    ///     .futures
    ///     .close_trade(running_trade.id())
    ///     .await?;
    ///
    /// assert_eq!(running_trade.id(), closed_trade.id());
    /// # Ok(())
    /// # }
    /// ```
    async fn close_trade(&self, id: Uuid) -> Result<Trade>;

    /// Close all running trades.
    ///
    /// **Required permission**: `Close and cancel trades`
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # #[allow(deprecated)]
    /// # async fn example(rest: lnm_sdk::api_v2::RestClient) -> Result<(), Box<dyn std::error::Error>> {
    /// use lnm_sdk::api_v2::models::Trade;
    ///
    /// let closed_trades: Vec<Trade> = rest.futures.close_all_trades().await?;
    /// # Ok(())
    /// # }
    /// ```
    async fn close_all_trades(&self) -> Result<Vec<Trade>>;

    /// Get the futures ticker.
    ///
    /// **Public endpoint** - No authentication required.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # #[allow(deprecated)]
    /// # async fn example(rest: lnm_sdk::api_v2::RestClient) -> Result<(), Box<dyn std::error::Error>> {
    /// use lnm_sdk::api_v2::models::Ticker;
    ///
    /// let ticker: Ticker = rest.futures.ticker().await?;
    /// # Ok(())
    /// # }
    async fn ticker(&self) -> Result<Ticker>;

    /// Modify the stoploss of an open/running trade.
    ///
    /// **Required permission**: `Modify trades`
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # #[allow(deprecated)]
    /// # async fn example(rest: lnm_sdk::api_v2::RestClient) -> Result<(), Box<dyn std::error::Error>> {
    /// use lnm_sdk::api_v2::models::{
    ///     Leverage, Price, Quantity, Trade, TradeExecution, TradeSide, TradeSize,
    /// };
    ///
    /// let open_trade: Trade = rest
    ///     .futures
    ///     .create_new_trade(
    ///         TradeSide::Buy,
    ///         TradeSize::from(Quantity::try_from(10)?),
    ///         Leverage::try_from(1)?,
    ///         TradeExecution::Limit(Price::try_from(10_000)?),
    ///         None,
    ///         None,
    ///     )
    ///     .await?;
    ///
    /// // Assuming trade is still open or running
    ///
    /// let new_stoploss = Price::try_from(9_000)?;
    /// let updated_trade: Trade = rest
    ///     .futures
    ///     .update_trade_stoploss(open_trade.id(), new_stoploss)
    ///     .await?;
    ///
    /// assert_eq!(updated_trade.stoploss().unwrap(), new_stoploss);
    /// # Ok(())
    /// # }
    /// ```
    async fn update_trade_stoploss(&self, id: Uuid, stoploss: Price) -> Result<Trade>;

    /// Modify the takeprofit of an open/running trade.
    ///
    /// **Required permission**: `Modify trades`
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # #[allow(deprecated)]
    /// # async fn example(rest: lnm_sdk::api_v2::RestClient) -> Result<(), Box<dyn std::error::Error>> {
    /// use lnm_sdk::api_v2::models::{
    ///     Leverage, Price, Quantity, Trade, TradeExecution, TradeSide, TradeSize,
    /// };
    ///
    /// let open_trade: Trade = rest
    ///     .futures
    ///     .create_new_trade(
    ///         TradeSide::Buy,
    ///         TradeSize::from(Quantity::try_from(10)?),
    ///         Leverage::try_from(1)?,
    ///         TradeExecution::Limit(Price::try_from(10_000)?),
    ///         None,
    ///         None,
    ///     )
    ///     .await?;
    ///
    /// // Assuming trade is still open or running
    ///
    /// let new_takeprofit = Price::try_from(11_000)?;
    /// let updated_trade: Trade = rest
    ///     .futures
    ///     .update_trade_takeprofit(open_trade.id(), new_takeprofit)
    ///     .await?;
    ///
    /// assert_eq!(updated_trade.takeprofit().unwrap(), new_takeprofit);
    /// # Ok(())
    /// # }
    /// ```
    async fn update_trade_takeprofit(&self, id: Uuid, takeprofit: Price) -> Result<Trade>;

    /// Adds margin to an open/running trade, increasing the collateral and therefore reducing the
    /// leverage.
    ///
    /// **Required permission**: `Modify trades`
    ///
    /// The resulting [`Leverage`] must be valid (≥ 1) after the update. To target a specific
    /// liquidation price, see [`trade_util::evaluate_collateral_delta_for_liquidation`].
    ///
    /// leverage = (quantity * SATS_PER_BTC) / (margin * price)
    ///
    /// Beware of potential rounding issues when evaluating the new leverage.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # #[allow(deprecated)]
    /// # async fn example(rest: lnm_sdk::api_v2::RestClient) -> Result<(), Box<dyn std::error::Error>> {
    /// use std::num::NonZeroU64;
    /// use lnm_sdk::api_v2::models::{
    ///     Leverage, Margin, Price, Trade, TradeExecution, TradeSide, TradeSize,
    /// };
    ///
    /// let created_trade: Trade = rest
    ///     .futures
    ///     .create_new_trade(
    ///         TradeSide::Buy,
    ///         TradeSize::from(Margin::try_from(10_000)?),
    ///         Leverage::try_from(2)?,
    ///         TradeExecution::Limit(Price::try_from(10_000)?),
    ///         None,
    ///         None,
    ///     )
    ///     .await?;
    ///
    /// // Assuming trade is still open or running
    ///
    /// let amount = NonZeroU64::try_from(1000)?;
    /// let updated_trade: Trade = rest
    ///     .futures
    ///     .add_margin(created_trade.id(), amount)
    ///     .await?;
    ///
    /// assert_eq!(updated_trade.id(), created_trade.id());
    /// assert_eq!(updated_trade.margin().as_u64(), 11_000);
    /// assert!(updated_trade.leverage() < created_trade.leverage());
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// [`trade_util::evaluate_collateral_delta_for_liquidation`]: crate::api_v2::models::trade_util::evaluate_collateral_delta_for_liquidation
    async fn add_margin(&self, id: Uuid, amount: NonZeroU64) -> Result<Trade>;

    /// Removes funds from a trade, decreasing the collateral and possibly increasing the leverage.
    ///
    /// **Required permission**: `Modify trades`
    ///
    /// Funds are first removed from the trade's PL, if positive, by adjusting the trade's entry
    /// price. Then, they are removed from the trade's margin.
    /// The resulting [`Leverage`] must be valid (≥ 1 and ≤ 100) after the update. To target a
    /// specific liquidation price, see [`trade_util::evaluate_collateral_delta_for_liquidation`].
    ///
    /// leverage = (quantity * SATS_PER_BTC) / (margin * price)
    ///
    /// Beware of potential rounding issues when evaluating the new leverage.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # #[allow(deprecated)]
    /// # async fn example(rest: lnm_sdk::api_v2::RestClient) -> Result<(), Box<dyn std::error::Error>> {
    /// use std::num::NonZeroU64;
    /// use lnm_sdk::api_v2::models::{
    ///     Leverage, Margin, Price, Trade, TradeExecution, TradeSide, TradeSize,
    /// };
    ///
    /// let created_trade: Trade = rest
    ///     .futures
    ///     .create_new_trade(
    ///         TradeSide::Buy,
    ///         TradeSize::from(Margin::try_from(10_000)?),
    ///         Leverage::try_from(2)?,
    ///         TradeExecution::Limit(Price::try_from(10_000)?),
    ///         None,
    ///         None,
    ///     )
    ///     .await?;
    ///
    /// // Assuming trade is still open (no PL)
    ///
    /// let amount = NonZeroU64::try_from(1000)?;
    /// let updated_trade: Trade = rest
    ///     .futures
    ///     .cash_in(created_trade.id(), amount)
    ///     .await?;
    ///
    /// assert_eq!(updated_trade.id(), created_trade.id());
    /// assert_eq!(updated_trade.margin().as_u64(), 9_000);
    /// assert!(updated_trade.leverage() > created_trade.leverage());
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// [`trade_util::evaluate_collateral_delta_for_liquidation`]: crate::api_v2::models::trade_util::evaluate_collateral_delta_for_liquidation
    async fn cash_in(&self, id: Uuid, amount: NonZeroU64) -> Result<Trade>;
}

/// Methods for interacting with [LNM's v2 API]'s REST User endpoints.
///
/// This trait is sealed and not meant to be implemented outside of `lnm-sdk`.
///
/// [LNM's v2 API]: https://docs.lnmarkets.com/api/#overview
#[async_trait]
pub trait UserRepository: crate::sealed::Sealed + Send + Sync {
    /// Get user information.
    ///
    /// **Required permission**: `Retrieve user information`
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # #[allow(deprecated)]
    /// # async fn example(rest: lnm_sdk::api_v2::RestClient) -> Result<(), Box<dyn std::error::Error>> {
    /// use lnm_sdk::api_v2::models::User;
    ///
    /// let user: User = rest.user.get_user().await?;
    /// # Ok(())
    /// # }
    /// ```
    async fn get_user(&self) -> Result<User>;
}
