pub mod context;
mod handler;
mod message;
mod router;

use std::collections::HashMap;

pub use crate::prelude::derives;

use crate::{chaincode::context::Context, fabric::protos::ChaincodeId};
use prost::Message;
use serde::{Deserialize, Serialize};

/// connection.json representation provided by the peer
#[derive(Debug, Serialize, Deserialize)]
pub struct Metadata {
    /// The unique ID associated with the chaincode package.
    pub chaincode_id: String,
    /// The local mspid of the peer.
    pub mspid: String,
    /// The address in host:port format of the ChaincodeSupport gRPC server endpoint hosted by the peer.
    pub peer_address: String,
    /// The PEM encoded TLS client certificate generated by the peer that must be used when the chaincode establishes its connection to the peer.
    pub client_cert: String,
    /// The PEM encoded client key generated by the peer that must be used when the chaincode establishes its connection to the peer.
    pub client_key: String,
    /// The PEM encoded TLS root certificate for the ChaincodeSupport gRPC server endpoint hosted by the peer.
    pub root_cert: String,
}

pub trait Callable {
    fn call(
        &self,
        ctx: Context,
        args: Vec<String>,
    ) -> tokio::task::JoinHandle<Result<String, String>>;
    fn name(&self) -> &str;
}

pub struct Launcher {
    metadata: Metadata,
    chaincode_id: ChaincodeId,
    contracts: HashMap<String, HashMap<String, Box<dyn Callable>>>,
}

impl Launcher {
    /// Registers a new contract.
    /// * `contract_name` - The name of the contract. `""` refers to the default contract.
    /// * `functions` - Using `routes![]` to pass the path to the functions.
    ///
    /// ```rust
    /// fabric_sdk::chaincode::initialize()
    ///    .register(
    ///    "basic",
    ///    route![asset::create_asset]
    ///    )
    ///    .register(
    ///    "based",
    ///    route![asset::create_another_asset]
    ///    )
    ///    .launch();
    /// ```
    pub fn register(mut self, contract_name: &str, functions: Vec<Box<dyn Callable>>) -> Self {
        if self
            .contracts
            .insert(contract_name.to_string(), HashMap::new())
            .is_some()
        {
            panic!("Cannot insert same contract twice");
        }
        let contract = self
            .contracts
            .get_mut(contract_name)
            .expect("Couldn't get contract");
        for function in functions {
            contract.insert(function.name().to_string(), function);
        }
        self
    }
    pub fn launch(self) {
        tokio::runtime::Builder::new_multi_thread()
            .enable_all()
            .build()
            .expect("Unable to start tokio runtime")
            .block_on(async {
                let message_handler =
                    handler::MessageHandler::new(&self.metadata, self.chaincode_id, self.contracts)
                        .await;
                message_handler.run().await;
                println!("Message handler exited");
            });
    }
}
pub fn initialize() -> Launcher {
    let metadata = std::env::var("METADATA").unwrap();
    let metadata =
        serde_json::from_str::<Metadata>(metadata.as_str()).expect("Invalid metadata json");
    let vec = metadata.chaincode_id.clone().encode_to_vec();
    let buf = vec.as_slice();
    let chaincode_id = ChaincodeId::decode(buf).expect("No valid chaincode id");

    Launcher {
        metadata,
        chaincode_id,
        contracts: HashMap::new(),
    }
}
