use std::{
    error::Error,
    ops::{Bound, RangeBounds},
};

use futures::{future::BoxFuture, stream::BoxStream};
pub use hopr_types::{
    internal::prelude::{ChannelDirection, ChannelEntry, ChannelId, ChannelStatusDiscriminants},
    primitive::prelude::HoprBalance,
};
use hopr_types::{
    internal::prelude::{ChannelStatus, generate_channel_id},
    primitive::prelude::Address,
};
use strum::IntoDiscriminant;

use crate::chain::ChainReceipt;

/// Alias for `chrono::DateTime<chrono::Utc>`.
pub type DateTime = chrono::DateTime<chrono::Utc>;

/// Selector for channels.
///
/// See [`ChainReadChannelOperations::stream_channels`].
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct ChannelSelector {
    /// Filter by source address.
    pub source: Option<Address>,
    /// Filter by destination address
    pub destination: Option<Address>,
    /// Filter by channel id.
    pub id: Option<ChannelId>,
    /// Filter by possible channel states.
    pub allowed_states: Vec<ChannelStatusDiscriminants>,
    /// Range of closure times if `PendingToClose` was specified in `allowed_states`,
    /// otherwise has no effect.
    pub closure_time_range: (Bound<DateTime>, Bound<DateTime>),
}

impl Default for ChannelSelector {
    fn default() -> Self {
        Self {
            source: None,
            destination: None,
            id: None,
            allowed_states: vec![],
            closure_time_range: (Bound::Unbounded, Bound::Unbounded),
        }
    }
}

impl ChannelSelector {
    /// Sets the `source` bound on a channel.
    ///
    /// If `id` was previously set, it will be unset.
    #[must_use]
    pub fn with_source<A: Into<Address>>(mut self, address: A) -> Self {
        self.source = Some(address.into());
        self.id = None;
        self
    }

    /// Sets the `destination` bound on a channel.
    ///
    /// If `id` was previously set, it will be unset.
    #[must_use]
    pub fn with_destination<A: Into<Address>>(mut self, address: A) -> Self {
        self.destination = Some(address.into());
        self.id = None;
        self
    }

    /// Sets the `id` bound on a channel.
    ///
    /// If `source` or `destination` were previously set, they will be unset.
    #[must_use]
    pub fn with_id(mut self, id: ChannelId) -> Self {
        self.id = Some(id);
        self.source = None;
        self.destination = None;
        self
    }

    /// Sets the allowed channel states.
    #[must_use]
    pub fn with_allowed_states(mut self, allowed_states: &[ChannelStatusDiscriminants]) -> Self {
        self.allowed_states.extend_from_slice(allowed_states);
        self
    }

    /// Sets the channel closure range.
    ///
    /// This has an effect only if `PendingToClose` is set in the allowed states.
    #[must_use]
    pub fn with_closure_time_range<T: RangeBounds<DateTime>>(mut self, range: T) -> Self {
        self.closure_time_range = (range.start_bound().cloned(), range.end_bound().cloned());
        self
    }

    /// Convenience method to include all effectively open channels -
    /// channels which are open or pending to close with closure time in the future with at least `min_grace_period`.
    ///
    /// In these channels, ticket redemption of incoming tickets is still possible.
    #[must_use]
    pub fn with_redeemable_channels(self, min_grace_period: Option<std::time::Duration>) -> Self {
        self.with_allowed_states(&[
            ChannelStatusDiscriminants::Open,
            ChannelStatusDiscriminants::PendingToClose,
        ])
        .with_closure_time_range(chrono::Utc::now() + min_grace_period.unwrap_or_default()..)
    }

    /// Checks if the given [`channel`](ChannelEntry) satisfies the selector.
    pub fn satisfies(&self, channel: &ChannelEntry) -> bool {
        if let Some(id) = &self.id {
            if channel.get_id() != id {
                return false;
            }
        } else {
            if let Some(source) = &self.source
                && channel.source != *source
            {
                return false;
            }

            if let Some(dst) = &self.destination
                && channel.destination != *dst
            {
                return false;
            }
        }

        if !self.allowed_states.is_empty() && !self.allowed_states.contains(&channel.status.discriminant()) {
            return false;
        }

        if self
            .allowed_states
            .contains(&ChannelStatusDiscriminants::PendingToClose)
            && let ChannelStatus::PendingToClose(time) = &channel.status
        {
            let time = DateTime::from(*time);
            if !self.closure_time_range.contains(&time) {
                return false;
            }
        }

        true
    }
}

/// On-chain read operations regarding channels.
#[auto_impl::auto_impl(&, Box, Arc)]
pub trait ChainReadChannelOperations {
    type Error: Error + Send + Sync + 'static;

    /// Returns on-chain [`Address`] of the current node.
    fn me(&self) -> &Address;

    /// Returns a single channel given `src` and `dst`.
    fn channel_by_parties(&self, src: &Address, dst: &Address) -> Result<Option<ChannelEntry>, Self::Error> {
        self.channel_by_id(&generate_channel_id(src, dst))
    }

    /// Returns a single channel given `channel_id`.
    fn channel_by_id(&self, channel_id: &ChannelId) -> Result<Option<ChannelEntry>, Self::Error>;

    /// Returns a stream of channels given the [`ChannelSelector`].
    fn stream_channels<'a>(&'a self, selector: ChannelSelector) -> Result<BoxStream<'a, ChannelEntry>, Self::Error>;
}

/// On-chain write operations regarding channels.
#[async_trait::async_trait]
#[auto_impl::auto_impl(&, Box, Arc)]
pub trait ChainWriteChannelOperations {
    type Error: Error + Send + Sync + 'static;
    /// Opens a channel with `dst` and `amount`.
    async fn open_channel<'a>(
        &'a self,
        dst: &'a Address,
        amount: HoprBalance,
    ) -> Result<BoxFuture<'a, Result<ChainReceipt, Self::Error>>, Self::Error>;

    /// Funds an existing channel.
    async fn fund_channel<'a>(
        &'a self,
        channel_id: &'a ChannelId,
        amount: HoprBalance,
    ) -> Result<BoxFuture<'a, Result<ChainReceipt, Self::Error>>, Self::Error>;

    /// Closes an existing channel.
    async fn close_channel<'a>(
        &'a self,
        channel_id: &'a ChannelId,
    ) -> Result<BoxFuture<'a, Result<ChainReceipt, Self::Error>>, Self::Error>;
}
