use std::sync::Arc;

use serde_json::{Value, json};

pub type LoggerInvoker = Arc<dyn Fn(&str, Value) + Send + Sync>;

#[derive(Clone, Default)]
pub struct Logger {
    invoker: Option<LoggerInvoker>,
    trace_id: String,
    function_name: String,
}

impl Logger {
    pub fn new(
        invoker: Option<LoggerInvoker>,
        trace_id: Option<String>,
        function_name: Option<String>,
    ) -> Self {
        Self {
            invoker,
            trace_id: trace_id.unwrap_or_default(),
            function_name: function_name.unwrap_or_default(),
        }
    }

    fn build_params(&self, message: &str, data: Option<Value>) -> Value {
        json!({
            "message": message,
            "trace_id": self.trace_id,
            "function_name": self.function_name,
            "data": data,
        })
    }

    pub fn info(&self, message: &str, data: Option<Value>) {
        if let Some(invoker) = &self.invoker {
            invoker("logger.info", self.build_params(message, data));
        }
        tracing::info!(function = %self.function_name, message = %message);
    }

    pub fn warn(&self, message: &str, data: Option<Value>) {
        if let Some(invoker) = &self.invoker {
            invoker("logger.warn", self.build_params(message, data));
        }
        tracing::warn!(function = %self.function_name, message = %message);
    }

    pub fn error(&self, message: &str, data: Option<Value>) {
        if let Some(invoker) = &self.invoker {
            invoker("logger.error", self.build_params(message, data));
        }
        tracing::error!(function = %self.function_name, message = %message);
    }

    pub fn debug(&self, message: &str, data: Option<Value>) {
        if let Some(invoker) = &self.invoker {
            invoker("logger.debug", self.build_params(message, data));
        }
        tracing::debug!(function = %self.function_name, message = %message);
    }
}

#[cfg(test)]
mod tests {
    use std::sync::{Arc, Mutex};

    use super::*;

    #[test]
    fn logger_invokes_with_expected_payload() {
        let calls: Arc<Mutex<Vec<(String, Value)>>> = Arc::new(Mutex::new(Vec::new()));
        let calls_ref = calls.clone();

        let invoker: LoggerInvoker = Arc::new(move |path, params| {
            calls_ref.lock().unwrap().push((path.to_string(), params));
        });

        let logger = Logger::new(
            Some(invoker),
            Some("trace-1".to_string()),
            Some("function-a".to_string()),
        );

        logger.info("hello", Some(json!({ "key": "value" })));
        logger.warn("warn", None);
        logger.error("oops", Some(json!(123)));

        let calls = calls.lock().unwrap();
        assert_eq!(calls.len(), 3);
        assert_eq!(calls[0].0, "logger.info");
        assert_eq!(calls[1].0, "logger.warn");
        assert_eq!(calls[2].0, "logger.error");

        assert_eq!(calls[0].1["message"], "hello");
        assert_eq!(calls[0].1["trace_id"], "trace-1");
        assert_eq!(calls[0].1["function_name"], "function-a");
        assert_eq!(calls[0].1["data"]["key"], "value");

        assert!(calls[1].1["data"].is_null());
        assert_eq!(calls[2].1["data"], 123);
    }
}
