// License: see LICENSE file at root directory of `master` branch

//! # Request verifier

use {
    std::{
        borrow::Cow,
        io::BufReader,
        net::TcpStream,
        sync::{Arc, mpsc},
    },

    bailamos::rpc,
    binn_ir::Value,
    blackhole::Job,
    native_tls::TlsAcceptor,

    crate::{
        db::users::{Passphrase, User},
        task::{
            RequestHandler,
            ResponseSender,
            db_manager::{self, MsgSender},
        },
    },
};

/// # Request verifier
pub struct RequestVerifier {
    tls_acceptor: Arc<TlsAcceptor>,
    stream: Option<TcpStream>,
    db_msg_sender: MsgSender,
}

impl RequestVerifier {

    /// # Makes new instance
    pub fn new(tls_acceptor: Arc<TlsAcceptor>, stream: TcpStream, db_msg_sender: MsgSender) -> Self {
        Self {
            tls_acceptor,
            stream: Some(stream),
            db_msg_sender,
        }
    }

}

impl Job for RequestVerifier {

    fn run(&mut self) -> Option<Box<dyn Job>> {
        match self.stream.take() {
            Some(stream) => match self.tls_acceptor.accept(stream) {
                Ok(stream) => {
                    let mut reader = BufReader::new(stream);
                    let (status_code, msg, data): (_, Cow<str>, _) = match rpc::read_request(&mut reader) {
                        Ok(mut content) => match (
                            content.remove(&rpc::REQUEST_PARAM_USER_NAME), content.remove(&rpc::REQUEST_PARAM_PASSPHRASE),
                        ) {
                            (Some(Value::Text(user_name)), Some(Value::Blob(passphrase))) => {
                                // Client only hashes passphrase once, we have to hash it one more time
                                let passphrase = Passphrase::from(tiny_keccak::sha3_512(&passphrase));
                                let user = User::new(None, user_name, passphrase, None);
                                let (sender, receiver) = mpsc::channel();
                                match self.db_msg_sender.send(db_manager::Msg::VerifyUser { user, result_sender: sender }) {
                                    Ok(()) => match receiver.recv() {
                                        Ok(Some(user_id)) => return Some(Box::new(
                                            RequestHandler::new(reader.into_inner(), self.db_msg_sender.clone(), content, user_id),
                                        )),
                                        Ok(None) => (rpc::STATUS_INVALID_INPUT, "Invalid credentials".into(), None),
                                        Err(err) => (rpc::STATUS_SERVER_ERROR, err.to_string().into(), None),
                                    },
                                    Err(err) => (rpc::STATUS_SERVER_ERROR, err.to_string().into(), None),
                                }
                            },
                            _ => (rpc::STATUS_INVALID_INPUT, "Invalid user name/passphrase".into(), None),
                        },
                        Err(err) => (rpc::STATUS_INVALID_INPUT, err.to_string().into(), None),
                    };
                    match rpc::make_response(status_code, msg, data) {
                        Ok(response) => return Some(Box::new(ResponseSender::new(reader.into_inner(), response))),
                        Err(err) => __e!("{}", err),
                    };
                },
                Err(err) => {
                    __e!("Handshaking failed: {:?}", err);
                    return None;
                },
            },
            None => print_internal_error!(),
        };

        None
    }

}
