use std::sync::Arc;

use parking_lot::Mutex;
use regex::Regex;
use kameleoon_trace_logging::trace_method;

use super::*;
use rstest::rstest;
use serial_test::serial;

struct SpyLogger {
    output: Arc<Mutex<Vec<(LogLevel, String)>>>,
}

impl SpyLogger {
    fn new() -> Self {
        Self {
            output: Arc::new(Mutex::new(Vec::new())),
        }
    }

    fn remove_trace_id(log: &str) -> String {
        static REGEX: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"#[\da-f]{4}").unwrap());
        return REGEX.replace(log, "").into_owned();
    }
}

impl super::Logger for SpyLogger {
    fn log(&self, log_level: LogLevel, message: &str) {
        if !message.contains("S::") {
            return;
        }
        let line = (log_level, Self::remove_trace_id(message));
        self.output.lock().push(line);
    }
}

#[derive(Debug)]
struct S {
    x: u32,
}

fn make_called_log(name: &str, args: &str) -> String {
    let log_level = KameleoonLogger::get_log_level();
    format!("Kameleoon [{log_level}]: CALL S::{name}({args})")
}

fn make_returned_log(name: &str, retval: &str) -> String {
    let log_level = KameleoonLogger::get_log_level();
    let mut log = format!("Kameleoon [{log_level}]: RET S::{name}(…)");
    if !retval.is_empty() {
        log.push_str(" → ");
        log.push_str(retval);
    }
    return log;
}

impl S {
    #[trace_method(LogLevel::Info)]
    fn new(x: u32) -> Self {
        Self { x }
    }
}

#[rstest]
#[case(LogLevel::Info, true)]
#[case(LogLevel::Warning, false)]
#[serial(logger)]
fn test_static_method(#[case] log_level: LogLevel, #[case] expect_output: bool) {
    // arrange
    let logger = SpyLogger::new();
    let output = logger.output.clone();
    KameleoonLogger::set_logger(Box::new(logger));
    KameleoonLogger::set_log_level(log_level);
    output.lock().clear();
    // act
    S::new(123);
    // assert
    let output_guard = output.lock();
    if expect_output {
        assert_eq!(2, output_guard.len());
        let (called_log_level, called_log) = &output_guard[0];
        assert_eq!(log_level, *called_log_level);
        assert_eq!(make_called_log("new", "x: 123"), *called_log);
        let (returned_log_level, returned_log) = &output_guard[1];
        assert_eq!(log_level, *returned_log_level);
        assert_eq!(make_returned_log("new", "S { x: 123 }"), *returned_log);
    } else {
        assert!(output_guard.is_empty());
    }
}

impl S {
    #[trace_method(LogLevel::Debug)]
    fn sum(&self, oth: &Self) -> Self {
        Self { x: self.x + oth.x }
    }
}

#[rstest]
#[case(LogLevel::Debug, true)]
#[case(LogLevel::Info, false)]
#[serial(logger)]
fn test_dynamic_method(#[case] log_level: LogLevel, #[case] expect_output: bool) {
    // arrange
    let logger = SpyLogger::new();
    let output = logger.output.clone();
    KameleoonLogger::set_logger(Box::new(logger));
    KameleoonLogger::set_log_level(log_level);
    let a = S::new(2);
    let b = S::new(3);
    output.lock().clear();
    // act
    a.sum(&b);
    // assert
    let output_guard = output.lock();
    if expect_output {
        assert_eq!(2, output_guard.len());
        let (called_log_level, called_log) = &output_guard[0];
        assert_eq!(log_level, *called_log_level);
        assert_eq!(make_called_log("sum", "self: S { x: 2 }, oth: S { x: 3 }"), *called_log);
        let (returned_log_level, returned_log) = &output_guard[1];
        assert_eq!(log_level, *returned_log_level);
        assert_eq!(make_returned_log("sum", "S { x: 5 }"), *returned_log);
    } else {
        assert!(output_guard.is_empty());
    }
}

impl S {
    #[trace_method(LogLevel::Debug, skip(a, b, retval))]
    fn sum0(a: &Self, b: &Self) -> Self {
        Self { x: a.x + b.x }
    }

    #[trace_method(LogLevel::Debug, skip(a))]
    fn sum1(a: &Self, b: &Self) -> Self {
        Self { x: a.x + b.x }
    }

    #[trace_method(LogLevel::Debug, skip(b))]
    fn sum2(a: &Self, b: &Self) -> Self {
        Self { x: a.x + b.x }
    }

    #[trace_method(LogLevel::Debug, skip(retval))]
    fn sum3(a: &Self, b: &Self) -> Self {
        Self { x: a.x + b.x }
    }
}

#[rstest]
#[serial(logger)]
fn test_skip_static() {
    // arrange
    let a = S::new(2);
    let b = S::new(3);
    let logger = SpyLogger::new();
    let output = logger.output.clone();
    KameleoonLogger::set_logger(Box::new(logger));
    KameleoonLogger::set_log_level(LogLevel::Debug);
    output.lock().clear();
    // act
    S::sum0(&a, &b);
    S::sum1(&a, &b);
    S::sum2(&a, &b);
    S::sum3(&a, &b);
    // assert
    let output_guard = output.lock();
    assert_eq!(8, output_guard.len());
    let mut output_iter = output_guard.iter().map(|(_lvl, msg)| msg);
    // assert skipped all
    assert_eq!(make_called_log("sum0", ""), *output_iter.next().unwrap());
    assert_eq!(make_returned_log("sum0", ""), *output_iter.next().unwrap());
    // assert skipped param a
    assert_eq!(make_called_log("sum1", "b: S { x: 3 }"), *output_iter.next().unwrap());
    assert_eq!(make_returned_log("sum1", "S { x: 5 }"), *output_iter.next().unwrap());
    // assert skipped param b
    assert_eq!(make_called_log("sum2", "a: S { x: 2 }"), *output_iter.next().unwrap());
    assert_eq!(make_returned_log("sum2", "S { x: 5 }"), *output_iter.next().unwrap());
    // assert skipped retval
    assert_eq!(make_called_log("sum3", "a: S { x: 2 }, b: S { x: 3 }"), *output_iter.next().unwrap());
    assert_eq!(make_returned_log("sum3", ""), *output_iter.next().unwrap());
}

impl S {
    #[trace_method(LogLevel::Debug, skip(self, retval, oth))]
    fn add0(&self, oth: &Self) -> Self {
        Self { x: self.x + oth.x }
    }

    #[trace_method(LogLevel::Debug, skip(oth))]
    fn add1(&self, oth: &Self) -> Self {
        Self { x: self.x + oth.x }
    }

    #[trace_method(LogLevel::Debug, skip(self))]
    fn add2(&self, oth: &Self) -> Self {
        Self { x: self.x + oth.x }
    }

    #[trace_method(LogLevel::Debug, skip(retval))]
    fn add3(&self, oth: &Self) -> Self {
        Self { x: self.x + oth.x }
    }
}

#[rstest]
#[serial(logger)]
fn test_skip_dynamic() {
    // arrange
    let a = S::new(2);
    let b = S::new(3);
    let logger = SpyLogger::new();
    let output = logger.output.clone();
    KameleoonLogger::set_logger(Box::new(logger));
    KameleoonLogger::set_log_level(LogLevel::Debug);
    output.lock().clear();
    // act
    a.add0(&b);
    a.add1(&b);
    a.add2(&b);
    a.add3(&b);
    // assert
    let output_guard = output.lock();
    assert_eq!(8, output_guard.len());
    let mut output_iter = output_guard.iter().map(|(_lvl, msg)| msg);
    // assert skipped all
    assert_eq!(make_called_log("add0", ""), *output_iter.next().unwrap());
    assert_eq!(make_returned_log("add0", ""), *output_iter.next().unwrap());
    // assert skipped param
    assert_eq!(make_called_log("add1", "self: S { x: 2 }"), *output_iter.next().unwrap());
    assert_eq!(make_returned_log("add1", "S { x: 5 }"), *output_iter.next().unwrap());
    // assert skipped self
    assert_eq!(make_called_log("add2", "oth: S { x: 3 }"), *output_iter.next().unwrap());
    assert_eq!(make_returned_log("add2", "S { x: 5 }"), *output_iter.next().unwrap());
    // assert skipped retval
    assert_eq!(make_called_log("add3", "self: S { x: 2 }, oth: S { x: 3 }"), *output_iter.next().unwrap());
    assert_eq!(make_returned_log("add3", ""), *output_iter.next().unwrap());
}
