use crate::time::current_time_millis;
use async_trait::async_trait;
use serde::Serialize;
use serde_repr::Serialize_repr;
use std::fmt;
#[derive(Clone, Debug, Serialize_repr, PartialEq, PartialOrd)]
#[repr(u8)]
pub enum Severity {
    
    Debug = 1,
    
    Verbose = 2,
    
    Info = 3,
    
    Warning = 4,
    
    Error = 5,
    
    Critical = 6,
}
pub type LogLevel = Severity;
impl Default for Severity {
    fn default() -> Self {
        Severity::Info
    }
}
impl std::str::FromStr for Severity {
    type Err = String;
    fn from_str(s: &str) -> Result<Severity, Self::Err> {
        match s {
            "debug" | "Debug" | "DEBUG" => Ok(Severity::Debug),
            "verbose" | "Verbose" | "VERBOSE" => Ok(Severity::Verbose),
            "info" | "Info" | "INFO" => Ok(Severity::Info),
            "warning" | "Warning" | "WARNING" => Ok(Severity::Warning),
            "error" | "Error" | "ERROR" => Ok(Severity::Error),
            "critical" | "Critical" | "CRITICAL" => Ok(Severity::Critical),
            _ => Err(format!("Invalid severity: {}", s)),
        }
    }
}
impl fmt::Display for Severity {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Severity::Debug => "Debug",
                Severity::Verbose => "Verbose",
                Severity::Info => "Info",
                Severity::Warning => "Warning",
                Severity::Error => "Error",
                Severity::Critical => "Critical",
            }
        )
    }
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LogEntry {
    
    pub timestamp: u64,
    
    pub severity: Severity,
    
    
    pub text: String,
    
    #[serde(skip_serializing_if = "Option::is_none")]
    pub category: Option<String>,
    
    #[serde(skip_serializing_if = "Option::is_none")]
    pub class_name: Option<String>,
    
    #[serde(skip_serializing_if = "Option::is_none")]
    pub method_name: Option<String>,
    
    #[serde(skip_serializing_if = "Option::is_none")]
    pub thread_id: Option<String>,
}
unsafe impl Send for LogEntry {}
impl fmt::Display for LogEntry {
    
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} {} {}", self.timestamp, self.severity, self.text)
    }
}
impl Default for LogEntry {
    fn default() -> LogEntry {
        LogEntry {
            timestamp: current_time_millis(),
            severity: Severity::Debug,
            text: String::from(""),
            category: None,
            class_name: None,
            method_name: None,
            thread_id: None,
        }
    }
}
#[derive(Serialize, Debug)]
#[serde(rename_all = "camelCase")]
struct CxLogMsg<'a> {
    
    pub private_key: &'a str,
    
    pub application_name: &'a str,
    
    pub subsystem_name: &'a str,
    
    pub log_entries: Vec<LogEntry>,
}
#[derive(Clone, Debug)]
struct CxErr {
    msg: String,
}
impl fmt::Display for CxErr {
    
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", &self.msg)
    }
}
impl std::error::Error for CxErr {}
#[derive(Debug)]
pub struct LogQueue {
    entries: Vec<LogEntry>,
}
impl Default for LogQueue {
    fn default() -> Self {
        Self {
            entries: Vec::new(),
        }
    }
}
impl LogQueue {
    
    pub fn new() -> Self {
        Self::default()
    }
    
    pub fn from(entries: Vec<LogEntry>) -> Self {
        Self { entries }
    }
    
    pub fn take(&mut self) -> Vec<LogEntry> {
        let mut ve: Vec<LogEntry> = Vec::new();
        ve.append(&mut self.entries);
        ve
    }
    
    pub fn is_empty(&self) -> bool {
        self.entries.is_empty()
    }
    
    pub fn clear(&mut self) {
        self.entries.clear();
    }
    
    pub fn log(&mut self, e: LogEntry) {
        self.entries.push(e)
    }
}
impl fmt::Display for LogQueue {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut buf = String::with_capacity(256);
        for entry in self.entries.iter() {
            if !buf.is_empty() {
                buf.push('\n');
            }
            buf.push_str(&entry.to_string());
        }
        write!(f, "{}", buf)
    }
}
#[async_trait(?Send)]
pub trait Logger: Send {
    
    async fn send(
        &self,
        sub: &'_ str,
        entries: Vec<LogEntry>,
    ) -> Result<(), Box<dyn std::error::Error>>;
}
#[derive(Debug)]
pub struct CoralogixConfig {
    
    pub api_key: &'static str,
    
    pub application_name: &'static str,
    
    pub endpoint: &'static str,
}
#[derive(Debug)]
pub struct CoralogixLogger {
    config: CoralogixConfig,
    client: reqwest::Client,
}
impl CoralogixLogger {
    
    pub fn init(config: CoralogixConfig) -> Result<Box<dyn Logger + Send>, reqwest::Error> {
        use reqwest::header::{self, HeaderValue, CONNECTION, CONTENT_TYPE};
        let mut headers = header::HeaderMap::new();
        
        headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
        
        headers.insert(CONNECTION, HeaderValue::from_static("close"));
        let client = reqwest::Client::builder()
            .default_headers(headers)
            .build()?;
        Ok(Box::new(Self { config, client }))
    }
}
#[async_trait(?Send)]
impl Logger for CoralogixLogger {
    
    
    async fn send(
        &self,
        sub: &'_ str,
        entries: Vec<LogEntry>,
    ) -> Result<(), Box<dyn std::error::Error>> {
        if !entries.is_empty() {
            let msg = CxLogMsg {
                subsystem_name: sub,
                log_entries: entries,
                private_key: self.config.api_key,
                application_name: self.config.application_name,
            };
            let resp = self
                .client
                .post(self.config.endpoint)
                .json(&msg)
                .send()
                .await
                .map_err(|e| CxErr { msg: e.to_string() })?;
            check_status(resp)
                .await
                .map_err(|e| CxErr { msg: e.to_string() })?;
        }
        Ok(())
    }
}
#[derive(Default, Debug)]
pub struct ConsoleLogger {}
impl ConsoleLogger {
    
    pub fn init() -> Box<dyn Logger + Send> {
        Box::new(ConsoleLogger::default())
    }
}
#[cfg(target_arch = "wasm32")]
#[async_trait(?Send)]
impl Logger for ConsoleLogger {
    
    async fn send(
        &self,
        sub: &'_ str,
        entries: Vec<LogEntry>,
    ) -> Result<(), Box<dyn std::error::Error>> {
        for e in entries.iter() {
            let msg = format!("{} {} {} {}", e.timestamp, sub, e.severity, e.text);
            web_sys::console::log_1(&wasm_bindgen::JsValue::from_str(&msg));
        }
        Ok(())
    }
}
#[cfg(not(target_arch = "wasm32"))]
#[async_trait(?Send)]
impl Logger for ConsoleLogger {
    
    async fn send(
        &self,
        sub: &'_ str,
        entries: Vec<LogEntry>,
    ) -> Result<(), Box<dyn std::error::Error>> {
        for e in entries.iter() {
            println!("{} {} {} {}", e.timestamp, sub, e.severity, e.text);
        }
        Ok(())
    }
}
async fn check_status(resp: reqwest::Response) -> Result<(), Box<dyn std::error::Error>> {
    let status = resp.status().as_u16();
    if status >= 200 && status < 300 {
        Ok(())
    } else {
        let body = resp.text().await.unwrap_or_default();
        Err(Box::new(Error::Cx(format!(
            "Logging Error: status:{} {}",
            status, body
        ))))
    }
}
#[derive(Debug)]
enum Error {
    
    Cx(String),
}
impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Error::Cx(s) => s,
            }
        )
    }
}
impl std::error::Error for Error {}