use assert_fs::{TempDir, assert::PathAssert as _, fixture::ChildPath, prelude::PathChild as _};
use escargot::CargoBuild;
use log::LevelFilter;
use predicates::prelude::*;
use rstest::rstest;
use rstest_reuse::{self, *};
use std::fmt::Write;
use std::path::{Path, PathBuf};

#[template]
fn default_level(
    #[values(
        LevelFilter::Error,
        LevelFilter::Warn,
        LevelFilter::Info,
        LevelFilter::Debug,
        LevelFilter::Trace
    )]
    default_level: LevelFilter,
) {
}

#[template]
fn filter_level_1(
    #[values(
        (Some(LevelFilter::Error), "ERROR:"),
        (Some(LevelFilter::Warn), "WARN:"),
        (Some(LevelFilter::Info), "INFO:"),
        (Some(LevelFilter::Debug), "DEBUG:"),
        (Some(LevelFilter::Trace), "TRACE:"),
        (None, "")
    )]
    filter_level_1: (Option<LevelFilter>, &str),
) {
}
#[template]
fn filter_level_2(
    #[values(
        (Some(LevelFilter::Error), "ERROR:"),
        (Some(LevelFilter::Warn), "WARN:"),
        (Some(LevelFilter::Info), "INFO:"),
        (Some(LevelFilter::Debug), "DEBUG:"),
        (Some(LevelFilter::Trace), "TRACE:"),
        (None, "")
    )]
    filter_level_2: (Option<LevelFilter>, &str),
) {
}

fn expected_log_regex(filter_level: LevelFilter) -> String {
    let timestamp_regex = r"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)";
    let mut expected_log = "^".to_string();
    if filter_level >= LevelFilter::Trace {
        write!(
            expected_log,
            r"\[{timestamp_regex} TRACE integration_test\] Some trace log\n"
        )
        .unwrap();
    }
    if filter_level >= LevelFilter::Debug {
        write!(
            expected_log,
            r"\[{timestamp_regex} DEBUG integration_test\] Some debug log\n"
        )
        .unwrap();
    }
    if filter_level >= LevelFilter::Info {
        write!(
            expected_log,
            r"\[{timestamp_regex} INFO integration_test\] Some info log\n"
        )
        .unwrap();
    }
    if filter_level >= LevelFilter::Warn {
        write!(
            expected_log,
            r"\[{timestamp_regex} WARN integration_test\] Some warn log\n"
        )
        .unwrap();
    }
    if filter_level >= LevelFilter::Error {
        write!(
            expected_log,
            r"\[{timestamp_regex} ERROR integration_test\] Some error log\n"
        )
        .unwrap();
    }
    write!(expected_log, "$").unwrap();
    expected_log
}

fn run_cli(default_level: LevelFilter, log_args: &[&str]) -> String {
    let mut args = log_args.to_vec();
    let default_level_str = default_level.to_string();
    args.extend(["--default-level", &default_level_str]);
    let output = CargoBuild::new()
        .example("integration_test")
        .current_release()
        .current_target()
        .run()
        .unwrap()
        .command()
        .args(args)
        .output()
        .unwrap();
    assert!(output.status.success());
    String::from_utf8(output.stderr).unwrap()
}

fn log_arg_stderr(level: &str) -> String {
    format!("{}stderr", level)
}

fn log_arg_file(level: &str, path: &Path) -> String {
    format!("{}file:{}", level, path.display())
}

#[apply(default_level)]
#[apply(filter_level_1)]
#[rstest]
fn stderr(default_level: LevelFilter, filter_level_1: (Option<LevelFilter>, &str)) {
    let actual_log = run_cli(default_level, &["--log", &log_arg_stderr(filter_level_1.1)]);
    let expected_level = filter_level_1.0.unwrap_or(default_level);
    let expected_log_regex = expected_log_regex(expected_level);
    assert!(
        predicates::str::is_match(expected_log_regex)
            .unwrap()
            .eval(&actual_log)
    );
}

struct TempLogFile {
    tempdir: TempDir,
}

impl TempLogFile {
    pub fn setup() -> Self {
        let tempdir = TempDir::new().unwrap();
        Self { tempdir }
    }

    pub fn logfile_path(&self) -> PathBuf {
        self.logfile().path().to_path_buf()
    }

    pub fn assert_was_created_with_content(&self, expected_log_regex: &str) {
        let log_file = self.logfile();
        log_file.assert(predicate::path::exists());
        log_file.assert(predicate::str::is_match(expected_log_regex).unwrap());
    }

    fn logfile(&self) -> ChildPath {
        self.tempdir.child("logfile")
    }
}

#[apply(default_level)]
#[apply(filter_level_1)]
#[rstest]
fn file(default_level: LevelFilter, filter_level_1: (Option<LevelFilter>, &str)) {
    let logfile = TempLogFile::setup();
    let stderr = run_cli(
        default_level,
        &[
            "--log",
            &log_arg_file(filter_level_1.1, &logfile.logfile_path()),
        ],
    );
    let expected_level = filter_level_1.0.unwrap_or(default_level);
    logfile.assert_was_created_with_content(&expected_log_regex(expected_level));
    assert_eq!("", stderr);
}

#[apply(default_level)]
#[apply(filter_level_1)]
#[apply(filter_level_2)]
#[rstest]
fn two_files(
    default_level: LevelFilter,
    filter_level_1: (Option<LevelFilter>, &str),
    filter_level_2: (Option<LevelFilter>, &str),
) {
    let logfile1 = TempLogFile::setup();
    let logfile2 = TempLogFile::setup();
    let stderr = run_cli(
        default_level,
        &[
            "--log",
            &log_arg_file(filter_level_1.1, &logfile1.logfile_path()),
            "--log",
            &log_arg_file(filter_level_2.1, &logfile2.logfile_path()),
        ],
    );
    let expected_level_1 = filter_level_1.0.unwrap_or(default_level);
    let expected_level_2 = filter_level_2.0.unwrap_or(default_level);
    logfile1.assert_was_created_with_content(&expected_log_regex(expected_level_1));
    logfile2.assert_was_created_with_content(&expected_log_regex(expected_level_2));
    assert_eq!("", stderr);
}

#[apply(default_level)]
#[apply(filter_level_1)]
#[apply(filter_level_2)]
#[rstest]
fn file_and_stderr(
    default_level: LevelFilter,
    filter_level_1: (Option<LevelFilter>, &str),
    filter_level_2: (Option<LevelFilter>, &str),
) {
    let logfile = TempLogFile::setup();
    let stderr = run_cli(
        default_level,
        &[
            "--log",
            &log_arg_file(filter_level_1.1, &logfile.logfile_path()),
            "--log",
            &log_arg_stderr(filter_level_2.1),
        ],
    );
    let expected_level_1 = filter_level_1.0.unwrap_or(default_level);
    let expected_level_2 = filter_level_2.0.unwrap_or(default_level);
    logfile.assert_was_created_with_content(&expected_log_regex(expected_level_1));
    assert!(
        predicates::str::is_match(expected_log_regex(expected_level_2))
            .unwrap()
            .eval(&stderr)
    );
}

#[rstest]
fn no_flag_uses_default_logging() {
    // The test binary default logging means log to stderr, but only WARN and ERROR.

    let stderr = run_cli(LevelFilter::Info, &[]);
    assert!(
        predicates::str::is_match(expected_log_regex(LevelFilter::Warn))
            .unwrap()
            .eval(&stderr)
    );
}

#[rstest]
fn disable_logging() {
    let stderr = run_cli(LevelFilter::Info, &["--log", "none"]);
    assert_eq!("", stderr);
}

#[rstest]
fn disable_logging_with_two_nones() {
    let stderr = run_cli(LevelFilter::Info, &["--log", "none", "--log", "none"]);
    assert_eq!("", stderr);
}

#[rstest]
fn none_destination_doesnt_disable_if_other_destination_is_present() {
    let stderr = run_cli(LevelFilter::Info, &["--log", "none", "--log", "stderr"]);
    assert!(
        predicates::str::is_match(expected_log_regex(LevelFilter::Info))
            .unwrap()
            .eval(&stderr)
    );
}

#[rstest]
fn levels_are_case_insensitive(
    #[values(
        (LevelFilter::Error, "ERROR:"),
        (LevelFilter::Error, "error:"),
        (LevelFilter::Error, "ErRoR:"),
        (LevelFilter::Warn, "WARN:"),
        (LevelFilter::Warn, "warn:"),
        (LevelFilter::Warn, "WaRn:"),
        (LevelFilter::Info, "INFO:"),
        (LevelFilter::Info, "info:"),
        (LevelFilter::Info, "InFo:"),
        (LevelFilter::Debug, "DEBUG:"),
        (LevelFilter::Debug, "debug:"),
        (LevelFilter::Debug, "DeBuG:"),
        (LevelFilter::Trace, "TRACE:"),
        (LevelFilter::Trace, "trace:"),
        (LevelFilter::Trace, "TrAcE:")
    )]
    filter_level: (LevelFilter, &str),
) {
    let stderr = run_cli(
        LevelFilter::Info,
        &["--log", &log_arg_stderr(filter_level.1)],
    );
    let expected_level = filter_level.0;
    assert!(
        predicates::str::is_match(expected_log_regex(expected_level))
            .unwrap()
            .eval(&stderr)
    );
}

// TODO Tests for logging to syslog
