use serde::{Deserialize, Serialize};

use crate::ln::amount::Amount;

/// Basically `bdk::Balance`, so that `lexe-common` doesn't need to depend on
/// `bdk`.
///
/// Partitions a wallet balance into different categories.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct OnchainBalance {
    /// All coinbase outputs not yet matured
    pub immature: bitcoin::Amount,
    /// Unconfirmed UTXOs generated by a wallet tx
    pub trusted_pending: bitcoin::Amount,
    /// Unconfirmed UTXOs received from an external wallet
    pub untrusted_pending: bitcoin::Amount,
    /// Confirmed and immediately spendable balance
    pub confirmed: bitcoin::Amount,
}

/// Classify the lightning channel balances into different categories.
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
pub struct LightningBalance {
    /// The sum channel value that is usable.
    ///
    /// For example, if we have an open channel with a disconnected node, then
    /// we can't send over it; it would not be counted here.
    pub usable: Amount,

    /// Our most accurate estimation of how much we can currently send if we
    /// made a MPP over all usable channels to an unspecified recipient.
    ///
    /// It's the sum of [`next_outbound_htlc_limit`] over all channels adjusted
    /// to account for an estimate of the total fees paid to Lexe's LSP and the
    /// broader Lightning Network.
    ///
    /// It's intended to be a conservative estimate, so the user should
    /// *usually* be able to send this amount. A precise sendable upper bound
    /// is not computable without knowledge of the recipient because routing
    /// fees vary by path.
    ///
    /// This value is bounded above by [`Self::max_sendable`].
    ///
    /// [`next_outbound_htlc_limit`]: crate::ln::channel::LxChannelDetails::next_outbound_htlc_limit
    pub sendable: Amount,

    /// The maximum amount that we could possibly send in a 'typical' payment,
    /// i.e. a multi-hop payment to anyone who is not a direct counterparty.
    ///
    /// This is computed as the sum of [`next_outbound_htlc_limit`] over all
    /// usable channels, then adjusted to account for the minimum fees Lexe's
    /// LSP could charge us.
    ///
    /// - It should not be possible to send more than this amount over any
    ///   multi-hop payment.
    /// - Exactly this amount may be sendable only in very specific scenarios,
    ///   such as paying another Lexe user.
    /// - Technically, it is possible to send [`sum(next_outbound_htlc_limit)`]
    ///   to our direct channel counterparties, but since User <-> LSP payments
    ///   are a special case, we'll handle those flows differently.
    ///
    /// [`next_outbound_htlc_limit`]: crate::ln::channel::LxChannelDetails::next_outbound_htlc_limit
    // TODO(max): We've confirmed we can send `next_outbound_htlc_limit`
    // between User <-> LSP in a single path single hop payment, but
    // sending up to `sum(next_outbound_htlc_limit)` hasn't yet been
    // confirmed for multi-path single hop payments.
    pub max_sendable: Amount,

    /// The sum channel value that isn't currently usable.
    ///
    /// The channel may be (1) opening and await confirmation, (2) shutting
    /// down, or (3) confirmed but the peer is disconnected.
    // TODO(phlip9): split these out
    pub pending: Amount,
}

// ---- impl OnchainBalance ---- //

impl OnchainBalance {
    pub const ZERO: Self = Self {
        immature: bitcoin::Amount::ZERO,
        trusted_pending: bitcoin::Amount::ZERO,
        untrusted_pending: bitcoin::Amount::ZERO,
        confirmed: bitcoin::Amount::ZERO,
    };

    /// The maximum amount that we could spend in a transaction right now.
    ///
    /// - This is generally what you should use, as it determines the total
    ///   actionable balance.
    /// - If it is necessary to partition funds by whether they are confirmed,
    ///   use [`Self::confirmed`] and [`Self::unconfirmed`].
    /// - "confirmed + unconfirmed = spendable"
    /// - "spendable + immature = total"
    pub fn spendable(&self) -> bitcoin::Amount {
        // We *can* in fact spend `untrusted_pending` outputs.
        // This is tested in a smoketest.
        self.confirmed + self.trusted_pending + self.untrusted_pending
    }

    /// The sum of mature but unconfirmed outputs sent to our wallet.
    ///
    /// - Includes trusted outputs from our own wallet (e.g. change outputs).
    ///   Use [`Self::untrusted_pending`] to get the sum of unconfirmed outputs
    ///   from external wallets.
    /// - "immature + unconfirmed + confirmed = total"
    pub fn unconfirmed(&self) -> bitcoin::Amount {
        self.trusted_pending + self.untrusted_pending
    }

    /// The maximum amount we can spend without risk of our transaction being
    /// cancelled by a counterparty double-spending one of our outputs.
    ///
    /// - Equivalent to BDK's `trusted_spendable`.
    /// - "trusted_spendable + untrusted_pending = spendable"
    pub fn trusted_spendable(&self) -> bitcoin::Amount {
        self.confirmed + self.trusted_pending
    }

    /// Sum of all unconfirmed + immature coins.
    ///
    /// - "pending + confirmed = total"
    pub fn pending(&self) -> bitcoin::Amount {
        self.immature + self.trusted_pending + self.untrusted_pending
    }

    /// The total balance visible to the wallet.
    ///
    /// NOTE: Generally use `spendable` instead of this, as immature outputs
    /// are not spendable (even though we should never see them in practice)
    pub fn total(&self) -> bitcoin::Amount {
        self.confirmed
            + self.trusted_pending
            + self.untrusted_pending
            + self.immature
    }
}

impl std::ops::Add for OnchainBalance {
    type Output = Self;

    fn add(self, other: Self) -> Self {
        Self {
            immature: self.immature + other.immature,
            trusted_pending: self.trusted_pending + other.trusted_pending,
            untrusted_pending: self.untrusted_pending + other.untrusted_pending,
            confirmed: self.confirmed + other.confirmed,
        }
    }
}

impl std::iter::Sum for OnchainBalance {
    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.fold(Self::ZERO, |a, b| a + b)
    }
}

impl Default for OnchainBalance {
    fn default() -> Self {
        Self::ZERO
    }
}

// ---- impl LightningBalance ---- //

impl LightningBalance {
    pub const ZERO: Self = Self {
        usable: Amount::ZERO,
        sendable: Amount::ZERO,
        max_sendable: Amount::ZERO,
        pending: Amount::ZERO,
    };

    /// Get the sum total of all our known channel balances.
    pub fn total(&self) -> Amount {
        self.usable + self.pending
    }
}

impl Default for LightningBalance {
    fn default() -> Self {
        Self::ZERO
    }
}
