use std::{ffi::OsString, fs::File, io::{self, Write}, sync::Mutex};

static LOG_LEVEL: Mutex<LogLevel> = Mutex::new(LogLevel::Info);
static LOG_FILE: Mutex<Option<OsString>> = Mutex::new(None);

pub use procmacro::log;

#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub enum LogLevel {
    Fatal,
    Error,
    Warning,
    Info,
    Debug,
    Trace,
}

pub struct DeferredLog {
    message: String,
    level: LogLevel,
    active: bool,
}

impl DeferredLog {
    fn new(message: &str, level: LogLevel) -> Self {
        Self {
            message: message.to_string(),
            level,
            active: true,
        }
    }

    pub fn cancel(&mut self) {
        self.active = false;
    }
}

impl Drop for DeferredLog {
    fn drop(&mut self) {
        if self.active && get_global_log_level() >= self.level {
            create_log(&self.message, self.level);
        }
    }
}

fn write(line: &str) -> io::Result<()> {
    let Some(path) = get_global_log_path() else {
        println!("{}", line);
        return Ok(());
    };

    let mut fio = File::options().append(true).create(true).open(path)?;
    fio.write_all(line.as_bytes())?;
    fio.write_all("\n".as_bytes())?;
    fio.flush()?;

    Ok(())
}

fn create_log(message: &str, level: LogLevel) {
    let log_id = utils::rng::random_numeric_string(24);

    #[rustfmt::skip]
    let prefix = match level {
        LogLevel::Fatal   => "[  FATAL  ",
        LogLevel::Error   => "[  ERROR  ",
        LogLevel::Warning => "[ WARNING ",
        LogLevel::Info    => "[  INFO   ",
        LogLevel::Debug   => "[  DEBUG  ",
        LogLevel::Trace   => "[  TRACE  ",
    }.to_string() + &utils::time::time_millis().to_string() + &format!("  {:0>24}", log_id) + " ]";

    write(&format!("{}", prefix)).expect("Failed to write log.");

    for (idx, line) in message.split("\n").enumerate() {
        if idx == 0 {
            write(&format!(" {line}")).expect("Failed to write log.");
        } else {
            write(&format!(" {idx:>7}  {log_id:0>3} | {line}")).expect("Failed to write log.");
        }
    }

    write("").expect("Failed to write log.");
}

macro_rules! logger_setup {
    ($level_fn_name:ident, $deferred_level_fn_name:ident, $level_variant:expr) => {
        pub fn $level_fn_name(message: &str) {
            if get_global_log_level() >= $level_variant {
                create_log(message, $level_variant);
            }
        }

        pub fn $deferred_level_fn_name(message: &str) -> DeferredLog {
            DeferredLog::new(message, $level_variant)
        }
    };
}

logger_setup! { trace, trace_deferred, LogLevel::Trace }
logger_setup! { debug, debug_deferred, LogLevel::Debug }
logger_setup! { info, info_deferred, LogLevel::Info}
logger_setup! { warn, warn_deferred, LogLevel::Warning }
logger_setup! { error, error_deferred, LogLevel::Error }
logger_setup! { fatal, fatal_deferred, LogLevel::Fatal }

pub fn set_global_log_level(level: LogLevel) {
    let mut guard = LOG_LEVEL.lock().expect("Logging level mutex is poisoned.");
    *guard = level;
}

pub fn get_global_log_level() -> LogLevel {
    *LOG_LEVEL.lock().expect("Logging level mutex is poisoned.")
}

pub fn set_global_log_path(path: Option<OsString>) {
    let mut guard = LOG_FILE.lock().expect("Logging path mutex is poisoned.");
    *guard = path;
}

pub fn get_global_log_path() -> Option<OsString> {
    LOG_FILE.lock().expect("Logging path mutex is poisoned.").clone()
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_level_ords() {
        assert!(LogLevel::Trace > LogLevel::Debug);
        assert!(LogLevel::Debug > LogLevel::Info);
        assert!(LogLevel::Info > LogLevel::Warning);
        assert!(LogLevel::Warning > LogLevel::Error);
        assert!(LogLevel::Error > LogLevel::Fatal);
    }
}
