use hopr_types::internal::routing::PathId;

use super::{MeasurablePath, MeasurablePeer};
use crate::graph::{MeasurableEdge, MeasurableNode};

/// The result of a transport-level probe over a transport path segment.
///
/// Contains the measured latency on success, or a unit error on failure.
pub type EdgeTransportMeasurement = std::result::Result<std::time::Duration, ()>;

/// The capacity of a payment channel representing an average amount of messages remaining in the channel.
pub type Capacity = u128;

/// Represents the different kinds of observations that can be recorded for a graph edge.
#[derive(Debug)]
pub enum EdgeWeightType {
    /// A direct transport measurement between this and another adjacent peer.
    Immediate(EdgeTransportMeasurement),
    /// A transport measurement relayed through an intermediate peer.
    Intermediate(EdgeTransportMeasurement),
    /// An update to the payment channel capacity along this edge.
    Capacity(Option<Capacity>),
    /// An update to the physical connectivity status of this edge.
    Connected(bool),
    /// An update to the immediate hop protocol conformance metrics (messages sent / acks received).
    ImmediateProtocolConformance {
        /// Total number of packets sent to the immediate peer.
        num_packets: u64,
        /// Total number of acknowledgments received from the immediate peer.
        num_acks: u64,
    },
}

/// Trait for recording new observations onto a graph edge.
pub trait EdgeObservableWrite {
    /// Records a new measurement or status update for this edge.
    fn record(&mut self, measurement: EdgeWeightType);
}

/// Trait for reading network-level properties of an edge.
pub trait EdgeNetworkObservableRead {
    /// Whether this edge represents also an existing physical connection between the peers.
    ///
    /// This is obviously settable only between the emitter of the measurement (this node) and
    /// arbitrary other node in the graph, but could be used for optimizations and path planning.
    fn is_connected(&self) -> bool;
}

/// Trait for reading HOPR protocol-level properties of an edge.
pub trait EdgeProtocolObservable {
    /// Capacity present in the channel to send through this path segment using PoR of HOPR protocol.
    fn capacity(&self) -> Option<u128>;
}

/// Trait for reading immediate hop protocol conformance metrics.
///
/// Tracks point-to-point message acknowledgment behavior between directly
/// connected peers. The ack rate can be used by cost functions to detect
/// adversarial nodes that drop or fail to acknowledge messages.
pub trait EdgeImmediateProtocolObservable {
    /// The ratio of acknowledged messages to sent messages for this immediate edge.
    ///
    /// Returns `None` when insufficient messages have been sent to compute
    /// a meaningful ratio. Returns `Some(rate)` in the range \[0.0, 1.0\]
    /// where 1.0 means all messages were acknowledged.
    fn ack_rate(&self) -> Option<f64>;
}

/// Trait for reading aggregated quality-of-service observations from a graph edge.
pub trait EdgeObservableRead {
    /// Measurement type for direct (1-hop) probes, including network connectivity and protocol conformance info.
    type ImmediateMeasurement: EdgeLinkObservable + EdgeNetworkObservableRead + EdgeImmediateProtocolObservable + Send;
    /// Measurement type for relayed probes through an intermediate, including channel capacity.
    type IntermediateMeasurement: EdgeLinkObservable + EdgeProtocolObservable + Send;

    /// The timestamp of the last update.
    fn last_update(&self) -> std::time::Duration;

    /// Transport level measurements between this node and any other node in the network.
    fn immediate_qos(&self) -> Option<&Self::ImmediateMeasurement>;

    /// Transport level measurements performed in a transparent mode using looping measurements.
    fn intermediate_qos(&self) -> Option<&Self::IntermediateMeasurement>;

    /// A value scoring the observed peer.
    ///
    /// It is from the [0.0, 1.0] range. The higher the value, the better the score.
    fn score(&self) -> f64;
}

/// Combined trait for full read/write access to edge observations.
///
/// Automatically implemented for any type that implements both [`EdgeObservableRead`]
/// and [`EdgeObservableWrite`].
pub trait EdgeObservable: EdgeObservableRead + EdgeObservableWrite {}

impl<T: EdgeObservableWrite + EdgeObservableRead> EdgeObservable for T {}

/// Trait for recording and querying transport-level link quality metrics for a transport link.
pub trait EdgeLinkObservable {
    /// Records a new result of the probe over this path segment.
    fn record(&mut self, measurement: EdgeTransportMeasurement);

    /// Returns average latency observed for the measured peer.
    fn average_latency(&self) -> Option<std::time::Duration>;

    /// A value representing the average success rate of probes.
    ///
    /// It is from the range [0.0, 1.0]. The higher the value, the better the score.
    fn average_probe_rate(&self) -> f64;

    /// A value scoring the observed peer.
    ///
    /// It is from the range [0.0, 1.0]. The higher the value, the better the score.
    fn score(&self) -> f64;
}

/// Lifecycle events observed for a node in the network.
#[derive(Debug, Clone)]
pub enum NodeObservation<T> {
    /// The node was discovered in the network.
    Discovered(T),
    /// A direct connection to the node was established.
    Connected(T),
    /// The direct connection to the node was lost.
    Disconnected(T),
}

/// Trait for recording node lifecycle observations into the graph.
pub trait NodeObservable {
    /// The node identifier type that can be measured as a peer.
    type Node: MeasurablePeer + Send;

    /// Record a new observation for the given node.
    fn record_node(&mut self, observation: NodeObservation<Self::Node>);
}

/// A trait specifying read-only graph view functionality.
///
/// Provides methods to inspect the graph topology: node membership, node count,
/// edge existence, and edge observation retrieval.
#[auto_impl::auto_impl(&, Box, Arc)]
pub trait NetworkGraphView {
    /// The concrete type of observations for peers.
    type Observed: EdgeObservable + Send;
    /// The identifier type used to reference nodes in the graph.
    type NodeId: Send;

    /// Returns the number of nodes in the graph.
    fn node_count(&self) -> usize;

    /// Checks whether the graph contains the given node.
    fn contains_node(&self, key: &Self::NodeId) -> bool;

    /// Returns a stream of all known nodes in the network graph.
    fn nodes(&self) -> futures::stream::BoxStream<'static, Self::NodeId>;

    /// Checks whether a directed edge exists between two nodes.
    ///
    /// The default implementation delegates to [`edge`](Self::edge).
    fn has_edge(&self, src: &Self::NodeId, dest: &Self::NodeId) -> bool {
        self.edge(src, dest).is_some()
    }

    /// Returns the weight represented by the observations for the edge between the
    /// given source and destination, if available.
    fn edge(&self, src: &Self::NodeId, dest: &Self::NodeId) -> Option<Self::Observed>;

    /// Returns the self-identity node of this graph.
    fn identity(&self) -> &Self::NodeId;
}

/// A trait for mutating the graph topology.
///
/// Provides methods to add/remove nodes and add edges.
#[auto_impl::auto_impl(&, Box, Arc)]
pub trait NetworkGraphWrite {
    /// The error type returned by fallible write operations.
    type Error;
    /// The concrete type of observations for peers.
    type Observed: EdgeObservable + Send;
    /// The identifier type used to reference nodes in the graph.
    type NodeId: Send;

    /// Adds a node to the graph if it does not already exist.
    fn add_node(&self, key: Self::NodeId);

    /// Removes a node and all its associated edges from the graph.
    fn remove_node(&self, key: &Self::NodeId);

    /// Adds a directed edge between two existing nodes with default observations.
    ///
    /// Returns an error if either node is not present in the graph.
    fn add_edge(&self, src: &Self::NodeId, dest: &Self::NodeId) -> Result<(), Self::Error>;

    /// Removes a directed edge between two nodes.
    ///
    /// If the edge does not exist, this operation has no effect.
    fn remove_edge(&self, src: &Self::NodeId, dest: &Self::NodeId);

    /// Updates an existing edge or inserts a new edge between two nodes.
    ///
    /// If the nodes do not exist, they are inserted into the graph.
    ///
    /// The provided closure `f` is applied to modify the edge's observations.
    /// If the edge already exists, its observations are updated.
    /// If the edge does not exist, it is created and the closure is applied.
    fn upsert_edge<F>(&self, src: &Self::NodeId, dest: &Self::NodeId, f: F)
    where
        F: FnOnce(&mut Self::Observed);
}

/// A trait for recording observed measurement updates to graph edges and nodes.
#[auto_impl::auto_impl(&, Box, Arc)]
pub trait NetworkGraphUpdate {
    /// Records an edge measurement derived from network telemetry.
    fn record_edge<N, P>(&self, update: MeasurableEdge<N, P>)
    where
        N: MeasurablePeer + Clone + Send + Sync + 'static,
        P: MeasurablePath + Clone + Send + Sync + 'static;

    /// Records a node observation derived from network telemetry.
    fn record_node<N>(&self, update: N)
    where
        N: MeasurableNode + Clone + Send + Sync + 'static;
}

/// A fold-like value function for graph traversal path scoring.
///
/// A **value function** produces scores where **higher is better** (to be maximized),
/// as opposed to a **cost function** where lower is better (to be minimized).
/// The accumulated value is folded over each edge in the path: edges that improve
/// the path increase the value, while poor-quality edges decrease it.
/// Paths whose value drops below [`min_value`](Self::min_value) are discarded.
#[allow(clippy::type_complexity)]
pub trait ValueFn {
    type Weight: EdgeObservableRead + Send;
    type Value: Clone + PartialOrd + Send + Sync;

    /// The initial value that will be modified by the value function.
    fn initial_value(&self) -> Self::Value;

    /// The minimum value, below which the value function will force discard upon traversal.
    fn min_value(&self) -> Option<Self::Value>;

    /// The value function accepting graph properties to establish the final value.
    fn into_value_fn(self) -> std::sync::Arc<dyn Fn(Self::Value, &Self::Weight, usize) -> Self::Value + Send + Sync>;
}

/// A trait specifying the graph traversal functionality.
///
/// Provides methods for finding simple paths between nodes in the network graph.
#[auto_impl::auto_impl(&, Box, Arc)]
pub trait NetworkGraphTraverse {
    /// The identifier type used to reference nodes in the graph.
    type NodeId: Send + Sync;
    /// The concrete edge observation type used by value functions during traversal.
    type Observed: EdgeObservableRead + Send;

    /// Returns a list of routes from the source to the destination with the specified length
    /// at the time of calling.
    ///
    /// The length argument specifies the number of edges in the graph, over which the path should
    /// be formed, i.e. source -> intermediate -> destination is 2 edges.
    ///
    /// The take count argument should be set in case the graph is expected to be large enough
    /// to be traversed slowly.
    fn simple_paths<V: ValueFn<Weight = Self::Observed>>(
        &self,
        source: &Self::NodeId,
        destination: &Self::NodeId,
        length: usize,
        take_count: Option<usize>,
        value_fn: V,
    ) -> Vec<(Vec<Self::NodeId>, PathId, V::Value)>;

    /// Returns a list of routes from the source to **any** reachable node with the
    /// specified edge length.
    ///
    /// Unlike [`simple_paths`](Self::simple_paths), this method does not target a
    /// specific destination. All graph nodes (except the source) are eligible
    /// destinations. The caller can further filter the results.
    fn simple_paths_from<V: ValueFn<Weight = Self::Observed>>(
        &self,
        source: &Self::NodeId,
        length: usize,
        take_count: Option<usize>,
        value_fn: V,
    ) -> Vec<(Vec<Self::NodeId>, PathId, V::Value)>;

    /// Return a list of nodes with a full loopback from myself to myself.
    ///
    /// The length argument specifies the number of edges in the graph, over which the path should
    /// be formed, i.e. source -> intermediate -> destination is 2 edges.
    ///
    /// At least length 2 is required to provide a path through a single relay.
    fn simple_loopback_to_self(&self, length: usize, take_count: Option<usize>) -> Vec<(Vec<Self::NodeId>, PathId)>;
}

/// Topology enumeration — which edges exist and which are reachable.
///
/// Unlike [`NetworkGraphTraverse`] (path planning), this trait answers
/// "what is connected to what" without computing routes.
#[auto_impl::auto_impl(&, Box, Arc)]
pub trait NetworkGraphConnectivity {
    /// The identifier type used to reference nodes in the graph.
    type NodeId: Send + Sync;
    /// The concrete edge observation type.
    type Observed: EdgeObservableRead + Send;

    /// Returns all edges in the graph as `(source, destination, observations)` triples.
    ///
    /// Only nodes that participate in at least one edge appear in the result.
    /// Isolated nodes (no incoming or outgoing edges) are omitted.
    fn connected_edges(&self) -> Vec<(Self::NodeId, Self::NodeId, Self::Observed)>;

    /// Returns edges reachable from the graph's
    /// [`identity`](NetworkGraphView::identity) node via directed traversal.
    ///
    /// Only edges where both the source and destination are reachable are
    /// included. Disconnected subgraphs that cannot be routed through are
    /// excluded.
    fn reachable_edges(&self) -> Vec<(Self::NodeId, Self::NodeId, Self::Observed)>;
}
