use std::io::{stderr, Write};

use log::{warn, LevelFilter};
use log4rs::{
    append::{
        console::ConsoleAppender,
        rolling_file::{
            policy::compound::{
                roll::fixed_window::FixedWindowRoller, trigger::size::SizeTrigger, CompoundPolicy,
            },
            RollingFileAppender,
        },
    },
    config::{Appender, Config, Root},
    encode::pattern::PatternEncoder,
    file::{Deserializers, RawConfig},
    init_config,
};
use serde_yaml::from_str;

use crate::{misc::Vfs, Error};

pub fn init(vfs: &Vfs, path: &str) {
    let (config, err) = match load_from_file(vfs, path) {
        Ok(config) => (config, None),
        Err(err) => (default_config(), Some(err)),
    };

    init_config(config).expect("Unable to initialize logger");

    if let Some(err) = err {
        warn!("Unable to load log config from file: {}", err);
    }
}

fn load_from_file(vfs: &Vfs, path: &str) -> Result<Config, Error> {
    let mut config = String::default();
    vfs.join(path)?.open_file()?.read_to_string(&mut config)?;

    let mut stderr = stderr();
    let config = from_str::<RawConfig>(&config)?;
    let deserializers = Deserializers::default();

    let (appenders, errors) = config.appenders_lossy(&deserializers);
    for error in &errors {
        let _ = writeln!(stderr, "log: {}", error);
    }

    let (config, errors) = Config::builder()
        .appenders(appenders)
        .loggers(config.loggers())
        .build_lossy(config.root());
    for error in &errors {
        let _ = writeln!(stderr, "log: {}", error);
    }

    Ok(config)
}

#[allow(clippy::identity_op)]
fn default_config() -> Config {
    let mut root = Root::builder();
    let mut config = Config::builder();

    let encoder = PatternEncoder::new("[{d} {h({l:>5})}] {m}{n}");
    let stdout = ConsoleAppender::builder()
        .encoder(Box::new(encoder))
        .build();
    root = root.appender("stdout");
    config = config.appender(Appender::builder().build("stdout", Box::new(stdout)));

    if let Ok(roller) = FixedWindowRoller::builder()
        .base(1)
        .build("space-crush-{}.log", 5)
    {
        let path = "space-crush.log";
        let trigger = SizeTrigger::new(1 * 1024 * 1024);
        let policy = CompoundPolicy::new(Box::new(trigger), Box::new(roller));
        let encoder = PatternEncoder::new(
            "{d} {h({l:<5})}{n}    Module:  {M}{n}    File:    {f}:{L}{n}    Message: {m}{n}",
        );

        if let Ok(file) = RollingFileAppender::builder()
            .append(true)
            .encoder(Box::new(encoder))
            .build(path, Box::new(policy))
        {
            root = root.appender("file");
            config = config.appender(Appender::builder().build("file", Box::new(file)));
        }
    }

    config
        .build(root.build(LevelFilter::Info))
        .expect("Unable to create default log config")
}
