use std::io;
use std::io::Write;
use log::Record;
use thiserror::Error;
use crate::formatter::Formatter;
#[cfg(all(feature = "json", feature = "kv"))]
use crate::kv::visitor::KeyValueSource;
#[cfg(feature = "time")]
use chrono::{Datelike, Timelike, Local};
#[cfg(feature = "color")]
use colored::Color;
#[cfg(feature = "color")]
use log::Level;
#[cfg(feature = "color")]
use colored::Colorize;

#[cfg(all(not(feature = "color"), not(feature = "time")))]
pub struct Pretty;

#[cfg(any(feature = "color", feature = "time"))]
pub struct Pretty {
    #[cfg(feature = "color")]
    colored: bool,
    #[cfg(feature = "time")]
    time: bool
}

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

    #[cfg(all(feature = "color", not(feature = "time")))]
    pub fn new(colored: bool) -> Self {
        Self {
            colored,
        }
    }

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

#[derive(Error, Debug)]
pub enum Error {
    #[error("failed to write to output: {0}")]
    WriteError(io::Error),
    #[cfg(all(feature = "json", feature = "kv"))]
    #[error("failed to serialize json: {0}")]
    SerializeError(serde_json::Error)
}

#[cfg(feature = "color")]
fn level_to_color(level: Level) -> Color {
    match level {
        Level::Error => Color::Red,
        Level::Warn => Color::Yellow,
        Level::Info => Color::Green,
        Level::Debug => Color::Blue,
        Level::Trace => Color::Cyan
    }
}

impl Formatter for Pretty {
    type Error = Error;

    fn format_log(&self, record: &Record, mut out: impl Write) -> Result<(), Self::Error> {
        let target = record.target();
        let level = record.level();
        #[cfg(feature = "color")]
        if self.colored {
            let color = level_to_color(level);
            write!(out, "[{}] {} ", level.as_str().color(color), target).map_err(Error::WriteError)?;
        } else {
            write!(out, "[{}] {} ", record.level(), target).map_err(Error::WriteError)?;
        };
        #[cfg(not(feature = "color"))]
        write!(out, "[{}] {} ", level, target).map_err(Error::WriteError)?;
        if let Some(module) = record.module_path().or(record.module_path_static()) && target != module {
            write!(out, "{} ", module).map_err(Error::WriteError)?;
        }

        #[cfg(feature = "time")]
        if self.time {
            let time = Local::now();
            write!(out, "{}-{}-{} {}:{}:{} ", time.year(), time.month(), time.day(), time.hour(), time.minute(), time.second()).map_err(Error::WriteError)?;
        }

        if let Some(file) = record.file().or(record.file_static()) {
            write!(out, "[{}", file).map_err(Error::WriteError)?;
            if let Some(line) = record.line() {
                write!(out, " {}]", line).map_err(Error::WriteError)?;
            } else {
                write!(out, "] ").map_err(Error::WriteError)?;
            }
        } else if let Some(line) = record.line() {
            write!(out, "[Line {}] ", line).map_err(Error::WriteError)?;
        }
        write!(out, "{}", record.args()).map_err(Error::WriteError)?;
        #[cfg(all(feature = "json", feature = "kv"))]
        let kv = record.key_values();
        #[cfg(all(feature = "json", feature = "kv"))]
        if kv.count() > 0 {
            write!(out, "[{}]", serde_json::to_string(&KeyValueSource(kv)).map_err(Error::SerializeError)?).map_err(Error::WriteError)?;
        };
        write!(out, "\n").map_err(Error::WriteError)?;
        Ok(())
    }
}