pub use proto::log::LogLevel as Level;
use parking_lot::{Mutex, MutexGuard};
use proto::log::log_service_client::LogServiceClient;
use proto::log::LogMessage;
use crate::client::Client;
use crate::models::{Model, ModelDefinition, session_identifier};
use crate::models::session_identifier::TonicResultExt;
use crate::services::{AuthorizedChannel, Channel};
use crate::TonicResult;

#[derive(Debug)]
pub struct Service(Mutex<LogServiceClient<AuthorizedChannel>>);

macro_rules! log_level {
    ($($vis:vis async fn $fn_name:ident(&$self:ident, $message:ident: String) -> TonicResult<session_identifier::Result<()>>;)*) => {
        $(
        $vis async fn $fn_name(&$self, $message: String) -> TonicResult<session_identifier::Result<()>> {
            $self.client().$fn_name($message.into_usable_request())
                .await
                .map(|response| response.into_inner().into_definition())
                .into_session_identifier_result()
        }
        )*
    };
}

impl Service {
    pub fn new(client: &Client) -> Self {
        Self(Mutex::new(LogServiceClient::with_interceptor(client.channel.clone(), client.interceptor.clone())))
    }

    fn client(&self) -> MutexGuard<LogServiceClient<AuthorizedChannel>> {
        self.0.lock()
    }

    pub async fn log(&self, level: Level, message: String) -> TonicResult<session_identifier::Result<()>> {
        self.client().log((level, message).into_usable_request())
            .await
            .map(|response| response.into_inner().into_definition())
            .into_session_identifier_result()
    }

    log_level! {
        pub async fn severe(&self, message: String) -> TonicResult<session_identifier::Result<()>>;
        pub async fn warning(&self,message: String) -> TonicResult<session_identifier::Result<()>>;
        pub async fn info(&self, message: String) -> TonicResult<session_identifier::Result<()>>;
        pub async fn config(&self, message: String) -> TonicResult<session_identifier::Result<()>>;
        pub async fn fine(&self, message: String) -> TonicResult<session_identifier::Result<()>>;
        pub async fn finer(&self, message: String) -> TonicResult<session_identifier::Result<()>>;
        pub async fn finest(&self, message: String) -> TonicResult<session_identifier::Result<()>>;
    }
}

impl Model<(Level, String)> for LogMessage {
    fn into_definition(self) -> (Level, String) {
        (Level::from_i32(self.level).expect("expected level to be valid"), self.text.expect("expected text to exist").into_definition())
    }
}

impl ModelDefinition<LogMessage> for (Level, String) {
    fn into_model(self) -> LogMessage {
        let (level, text) = self;
        LogMessage {
            level: level as i32,
            text: Some(text.into_model()),
        }
    }
}
