/* This file is part of DarkFi (https://dark.fi)
 *
 * Copyright (C) 2020-2025 Dyne.org foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

use std::{
    collections::{HashMap, HashSet},
    sync::Arc,
};

use log::{debug, error, info};
use smol::lock::Mutex;

use darkfi::{
    net::settings::Settings,
    rpc::{
        jsonrpc::JsonSubscriber,
        server::{listen_and_serve, RequestHandler},
        settings::RpcSettings,
    },
    system::{ExecutorPtr, StoppableTask, StoppableTaskPtr},
    Error, Result,
};

/// JSON-RPC server methods
mod rpc;

/// P2P net protocols
mod proto;
use proto::{DamP2pHandler, DamP2pHandlerPtr};

/// P2P network flooder
mod flooder;
use flooder::{DamFlooder, DamFlooderPtr};

/// Atomic pointer to the Denial-of-service Analysis Multitool node
pub type DamNodePtr = Arc<DamNode>;

/// Structure representing a Denial-of-service Analysis Multitool node
pub struct DamNode {
    /// P2P network protocols handler.
    p2p_handler: DamP2pHandlerPtr,
    /// A map of various subscribers exporting live info from the node
    subscribers: HashMap<&'static str, JsonSubscriber>,
    /// JSON-RPC connection tracker
    rpc_connections: Mutex<HashSet<StoppableTaskPtr>>,
    /// Network flooder
    flooder: DamFlooderPtr,
}

impl DamNode {
    pub async fn new(
        p2p_handler: DamP2pHandlerPtr,
        subscribers: HashMap<&'static str, JsonSubscriber>,
        flooder: DamFlooderPtr,
    ) -> DamNodePtr {
        Arc::new(Self {
            p2p_handler,
            subscribers,
            rpc_connections: Mutex::new(HashSet::new()),
            flooder,
        })
    }
}

/// Atomic pointer to the Denial-of-service Analysis Multitool daemon
pub type DamdPtr = Arc<Damd>;

/// Structure representing a Denial-of-service Analysis Multitool daemon
pub struct Damd {
    /// Darkfi node instance
    node: DamNodePtr,
    /// `dnet` background task
    dnet_task: StoppableTaskPtr,
    /// JSON-RPC background task
    rpc_task: StoppableTaskPtr,
}

impl Damd {
    /// Initialize a Denial-of-service Analysis Multitool daemon.
    ///
    /// Generates a new `DamNode` for provided configuration,
    /// along with all the corresponding background tasks.
    pub async fn init(net_settings: &Settings, ex: &ExecutorPtr) -> Result<DamdPtr> {
        info!(target: "damd::Damd::init", "Initializing a Denial-of-service Analysis Multitool daemon...");

        // Initialize P2P network
        let p2p_handler = DamP2pHandler::init(net_settings, ex).await?;

        // Here we initialize various subscribers that can export live network data.
        let mut subscribers = HashMap::new();
        subscribers.insert("dnet", JsonSubscriber::new("dnet.subscribe_events"));
        subscribers.insert("foo", JsonSubscriber::new("protocols.subscribe_foo"));
        subscribers.insert("attack_foo", JsonSubscriber::new("protocols.subscribe_attack_foo"));
        subscribers.insert("bar", JsonSubscriber::new("protocols.subscribe_bar"));
        subscribers.insert("attack_bar", JsonSubscriber::new("protocols.subscribe_attack_bar"));

        // Initialize flooder
        let flooder = DamFlooder::init(&p2p_handler.p2p, ex);

        // Initialize node
        let node = DamNode::new(p2p_handler, subscribers, flooder).await;

        // Generate the background tasks
        let dnet_task = StoppableTask::new();
        let rpc_task = StoppableTask::new();

        info!(target: "damd::Damd::init", "Denial-of-service Analysis Multitool daemon initialized successfully!");

        Ok(Arc::new(Self { node, dnet_task, rpc_task }))
    }

    /// Start the Denial-of-service Analysis Multitool daemon in the given executor,
    /// using the provided JSON-RPC configuration.
    pub async fn start(&self, executor: &ExecutorPtr, rpc_settings: &RpcSettings) -> Result<()> {
        info!(target: "damd::Damd::start", "Starting Denial-of-service Analysis Multitool daemon...");

        // Start the `dnet` task
        info!(target: "damd::Damd::start", "Starting dnet subs task");
        let dnet_sub_ = self.node.subscribers.get("dnet").unwrap().clone();
        let p2p_ = self.node.p2p_handler.p2p.clone();
        self.dnet_task.clone().start(
            async move {
                let dnet_sub = p2p_.dnet_subscribe().await;
                loop {
                    let event = dnet_sub.receive().await;
                    debug!(target: "damd::Damd::dnet_task", "Got dnet event: {:?}", event);
                    dnet_sub_.notify(vec![event.into()].into()).await;
                }
            },
            |res| async {
                match res {
                    Ok(()) | Err(Error::DetachedTaskStopped) => { /* Do nothing */ }
                    Err(e) => {
                        error!(target: "damd::Damd::start", "Failed starting dnet subs task: {}", e)
                    }
                }
            },
            Error::DetachedTaskStopped,
            executor.clone(),
        );

        // Start the JSON-RPC task
        info!(target: "damd::Damd::start", "Starting JSON-RPC server");
        let node_ = self.node.clone();
        self.rpc_task.clone().start(
            listen_and_serve(rpc_settings.clone(), self.node.clone(), None, executor.clone()),
            |res| async move {
                match res {
                    Ok(()) | Err(Error::RpcServerStopped) => node_.stop_connections().await,
                    Err(e) => error!(target: "damd::Damd::start", "Failed starting JSON-RPC server: {}", e),
                }
            },
            Error::RpcServerStopped,
            executor.clone(),
        );

        // Start the P2P network
        info!(target: "damd::Damd::start", "Starting P2P network");
        self.node.p2p_handler.clone().start(executor, &self.node.subscribers).await?;

        info!(target: "damd::Damd::start", "Denial-of-service Analysis Multitool daemon started successfully!");
        Ok(())
    }

    /// Stop the Denial-of-service Analysis Multitool daemon.
    pub async fn stop(&self) -> Result<()> {
        info!(target: "damd::Damd::stop", "Terminating Denial-of-service Analysis Multitool daemon...");

        // Stop the flooder
        info!(target: "damd::Damd::stop", "Stopping the flooder...");
        self.node.flooder.stop().await;

        // Stop the `dnet` node
        info!(target: "damd::Damd::stop", "Stopping dnet subs task...");
        self.dnet_task.stop().await;

        // Stop the JSON-RPC task
        info!(target: "damd::Damd::stop", "Stopping JSON-RPC server...");
        self.rpc_task.stop().await;

        // Stop the P2P network
        info!(target: "damd::Damd::stop", "Stopping P2P network protocols handler...");
        self.node.p2p_handler.stop().await;

        info!(target: "damd::Damd::stop", "Denial-of-service Analysis Multitool daemon terminated successfully!");
        Ok(())
    }
}
