use crate::formatter::Formatter;
use crate::kv::visitor::KeyValueSource;
use log::Record;
use serde::Serialize;
use std::fmt::Arguments;
use std::io::Write;
use std::io;
#[cfg(feature = "time")]
use chrono::{DateTime, Local};
use thiserror::Error;

#[cfg(feature = "time")]
pub struct JSON {
    time: bool
}

impl JSON {
    #[cfg(feature = "time")]
    pub fn new(time: bool) -> Self {
        Self {
            time,
        }
    }
}

#[cfg(not(feature = "time"))]
pub struct JSON;

#[derive(Serialize)]
pub struct Message<'a> {
    message: &'a Arguments<'a>,
    level: &'a str,
    target: &'a str,
    #[serde(skip_serializing_if = "Option::is_none")]
    file: Option<&'a str>,
    #[serde(skip_serializing_if = "Option::is_none")]
    line: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    module_path: Option<&'a str>,
    #[cfg(feature = "kv")]
    #[serde(skip_serializing_if = "Option::is_none")]
    key_value_source: Option<KeyValueSource<'a>>,
    #[cfg(feature = "time")]
    #[serde(skip_serializing_if = "Option::is_none")]
    time: Option<&'a str>
}

#[derive(Error, Debug)]
pub enum Error {
    #[error("failed to serialize json: {0}")]
    SerializationError(serde_json::Error),
    #[error("failed to write json: {0}")]
    FormatError(io::Error)
}

impl Formatter for JSON {
    type Error = Error;

    fn format_log(&self, record: &Record, mut formatter: impl Write) -> Result<(), Self::Error> {
        writeln!(formatter, "{}", serde_json::to_value(Message {
            message: record.args(),
            level: record.level().as_str(),
            target: record.target(),
            file: record.file().or(record.file_static()),
            line: record.line(),
            module_path: record.module_path().or(record.module_path_static()),
            #[cfg(feature = "kv")]
            key_value_source: (record.key_values().count() > 0).then_some(KeyValueSource(record.key_values())),
            #[cfg(feature = "time")]
            time: self.time.then_some(&*Local::now().to_rfc3339()),
        }).map_err(Error::SerializationError)?).map_err(Error::FormatError)?;
        Ok(())
    }
}
