use std::sync::Arc;

use crate::chaindef::BlockHash;
use crate::chaindef::BlockHeader;
use crate::discovery::announcer::PeerAnnouncer;
use crate::discovery::discoverer::PeerDiscoverer;
use crate::{config::Config, daemon, index};
use anyhow::Result;
use bitcoin_hashes::Hash;
use tokio::sync::Mutex;

pub struct App {
    index: index::Index,
    daemon: daemon::Daemon,
    banner: String,
    tip: Mutex<BlockHash>,
    discoverer: Arc<PeerDiscoverer>,
    announcer: Arc<PeerAnnouncer>,
}

impl App {
    pub fn new(
        index: index::Index,
        daemon: Arc<daemon::Daemon>,
        config: &Config,
        discoverer: Arc<PeerDiscoverer>,
        announcer: Arc<PeerAnnouncer>,
    ) -> Result<Arc<App>> {
        Ok(Arc::new(App {
            index,
            daemon: daemon.reconnect()?,
            banner: config.server_banner.clone(),
            tip: Mutex::new(BlockHash::all_zeros()),
            discoverer,
            announcer,
        }))
    }
    pub fn index(&self) -> &index::Index {
        &self.index
    }
    pub(crate) fn daemon(&self) -> &daemon::Daemon {
        &self.daemon
    }

    pub(crate) fn discoverer(&self) -> &PeerDiscoverer {
        &self.discoverer
    }

    pub(crate) fn announcer(&self) -> &PeerAnnouncer {
        &self.announcer
    }

    pub async fn first_update(&self) -> Result<()> {
        match self.index.update().await {
            Ok(_) =>
            /* discard new headers on first update */
            {
                Ok(())
            }
            Err(e) => Err(e),
        }
    }

    pub async fn update_blocks(&self) -> Result<(Vec<BlockHeader>, Option<BlockHeader>)> {
        let daemon_tip = self.daemon.getbestblockhash().await?;
        let new_block = *self.tip.lock().await != daemon_tip;
        if new_block {
            let (new_headers, new_tip) = self.index.update().await?;
            *self.tip.lock().await = new_tip.block_hash();
            Ok((new_headers, Some(new_tip)))
        } else {
            Ok((vec![], None))
        }
    }

    pub async fn get_banner(&self) -> Result<String> {
        Ok(format!(
            "{}\n{}",
            self.banner,
            self.daemon.get_subversion().await?
        ))
    }
}
