use std::{net::SocketAddr, sync::Arc};

use anyhow::Result;
use bitcoincash::Network;
use prometheus::HistogramVec;

use crate::{
    daemon::CookieGetter,
    errors::ConnectionError,
    metrics::{default_duration_buckets, default_size_buckets, Metrics},
    signal::NetworkNotifier,
};

use super::{p2pconnection::P2PConnection, rpcconnection::RPCConnection};

/**
 * BB8 RPC connection pool manager for bitcoind
 */
pub struct BitcoindRPCConnectionManager {
    addr: SocketAddr,
    cookie_getter: Arc<dyn CookieGetter>,
}

impl BitcoindRPCConnectionManager {
    pub fn new(addr: SocketAddr, cookie_getter: Arc<dyn CookieGetter>) -> Self {
        Self {
            addr,
            cookie_getter,
        }
    }
}

impl bb8::ManageConnection for BitcoindRPCConnectionManager {
    type Connection = crate::daemon::rpcconnection::RPCConnection;

    type Error = crate::errors::ConnectionError;

    async fn connect(&self) -> Result<Self::Connection, Self::Error> {
        RPCConnection::new(self.addr, self.cookie_getter.clone())
    }

    async fn is_valid(&self, conn: &mut Self::Connection) -> Result<(), Self::Error> {
        match conn.request("ping", &None).await {
            Ok(_) => Ok(()),
            Err(e) => Err(Self::Error {
                msg: format!("ping failed: {e}"),
            }),
        }
    }

    fn has_broken(&self, conn: &mut Self::Connection) -> bool {
        conn.is_broken()
    }
}

/**
 * BB8 P2P connection pool manager for bitcoind
 */
pub struct BitcoindP2PConnectionManager {
    network: Network,
    addr: SocketAddr,
    network_notifier: Arc<NetworkNotifier>,
    send_duration: Arc<HistogramVec>,
    recv_duration: Arc<HistogramVec>,
    parse_duration: Arc<HistogramVec>,
    recv_size: Arc<HistogramVec>,
}

impl BitcoindP2PConnectionManager {
    pub fn new(
        network: Network,
        addr: SocketAddr,
        network_notifier: Arc<NetworkNotifier>,
        metrics: &Metrics,
    ) -> Self {
        let send_duration = Arc::new(metrics.histogram_vec(
            "rostrum_p2p_send_duration",
            "Time spent sending p2p messages (in seconds)",
            &["step"],
            default_duration_buckets(),
        ));
        let recv_duration = Arc::new(metrics.histogram_vec(
            "rostrum_p2p_recv_duration",
            "Time spent receiving p2p messages (in seconds)",
            &["step"],
            default_duration_buckets(),
        ));
        let parse_duration = Arc::new(metrics.histogram_vec(
            "rostrum_p2p_parse_duration",
            "Time spent parsing p2p messages (in seconds)",
            &["step"],
            default_duration_buckets(),
        ));
        let recv_size = Arc::new(metrics.histogram_vec(
            "rostrum_p2p_recv_size",
            "Size of p2p messages read (in bytes)",
            &["message"],
            default_size_buckets(),
        ));

        Self {
            network,
            addr,
            network_notifier,
            send_duration,
            recv_duration,
            parse_duration,
            recv_size,
        }
    }
}

impl bb8::ManageConnection for BitcoindP2PConnectionManager {
    type Connection = crate::daemon::p2pconnection::P2PConnection;

    type Error = crate::errors::ConnectionError;

    async fn connect(&self) -> Result<Self::Connection, Self::Error> {
        P2PConnection::connect(
            self.network,
            self.addr,
            self.network_notifier.clone(),
            Arc::clone(&self.send_duration),
            Arc::clone(&self.recv_duration),
            Arc::clone(&self.parse_duration),
            Arc::clone(&self.recv_size),
        )
        .await
    }

    async fn is_valid(&self, conn: &mut Self::Connection) -> Result<(), Self::Error> {
        match conn.is_broken() {
            true => Ok(()),
            false => Err(ConnectionError {
                msg: "Connection broken".to_string(),
            }),
        }
    }

    fn has_broken(&self, conn: &mut Self::Connection) -> bool {
        conn.is_broken()
    }
}
