use {
    crate::{
        config::error::SpannedError,
        toml::{
            toml_parser::{ErrorHandler, ParserError, parse},
            toml_span::{Span, Spanned, SpannedExt},
            toml_value::Value,
        },
    },
    bstr::{BStr, ByteSlice},
    std::{
        convert::Infallible,
        os::unix::ffi::OsStrExt,
        panic::{AssertUnwindSafe, catch_unwind},
        str::FromStr,
    },
    walkdir::WalkDir,
};

#[test]
fn test() {
    let mut have_failures = false;
    let mut num = 0;
    for path in WalkDir::new("./toml-test/tests/valid") {
        let path = path.unwrap();
        if let Some(prefix) = path.path().as_os_str().as_bytes().strip_suffix(b".toml") {
            num += 1;
            let res = catch_unwind(AssertUnwindSafe(|| {
                have_failures |= run_test(prefix.as_bstr());
            }));
            if res.is_err() {
                eprintln!("panic while running {}", prefix.as_bstr());
            }
        }
    }
    if have_failures {
        panic!("There were test failures");
    }
    eprintln!("ran {num} tests");
}

fn run_test(prefix: &BStr) -> bool {
    let toml = std::fs::read(&format!("{}.toml", prefix)).unwrap();
    let json = std::fs::read_to_string(&format!("{}.json", prefix)).unwrap();

    let json: serde_json::Value = serde_json::from_str(&json).unwrap();
    let json_as_toml = json_to_value(json);
    let toml = match parse(toml.as_bytes(), &NoErrorHandler(prefix, &toml)) {
        Ok(t) => t,
        Err(e) => {
            eprintln!("toml could not be parsed in test {}", prefix);
            NoErrorHandler(prefix, &toml).handle(e);
            return true;
        }
    };

    if toml != json_as_toml {
        eprintln!("toml and json differ in test {}", prefix);
        eprintln!("toml: {:#?}", toml);
        eprintln!("json: {:#?}", json_as_toml);
        true
    } else {
        false
    }
}

fn json_to_value(json: serde_json::Value) -> Spanned<Value> {
    let span = Span { lo: 0, hi: 0 };
    let val = match json {
        serde_json::Value::String(_)
        | serde_json::Value::Number(_)
        | serde_json::Value::Null
        | serde_json::Value::Bool(_) => panic!("Unexpected type"),
        serde_json::Value::Array(v) => Value::Array(v.into_iter().map(json_to_value).collect()),
        serde_json::Value::Object(v) => {
            if v.len() == 2 && v.contains_key("type") && v.contains_key("value") {
                let ty = v.get("type").unwrap().as_str().unwrap();
                let val = v.get("value").unwrap().as_str().unwrap();
                match ty {
                    "string" => Value::String(val.to_owned()),
                    "integer" => Value::Integer(i64::from_str(val).unwrap()),
                    "float" => Value::Float(f64::from_str(val).unwrap()),
                    "bool" => Value::Boolean(bool::from_str(val).unwrap()),
                    _ => panic!("unexpected type {}", ty),
                }
            } else {
                Value::Table(
                    v.into_iter()
                        .map(|(k, v)| (k.spanned(span), json_to_value(v)))
                        .collect(),
                )
            }
        }
    };
    val.spanned(span)
}

struct NoErrorHandler<'a>(&'a BStr, &'a [u8]);

impl<'a> ErrorHandler for NoErrorHandler<'a> {
    fn handle(&self, err: Spanned<ParserError>) {
        eprintln!(
            "{}: An error occurred during validation: {}",
            self.0,
            SpannedError {
                input: self.1.into(),
                span: err.span,
                cause: Some(err.value),
            }
        );
    }

    fn redefinition(&self, err: Spanned<ParserError>, prev: Span) {
        eprintln!(
            "{}: Redefinition: {}",
            self.0,
            SpannedError {
                input: self.1.into(),
                span: err.span,
                cause: Some(err.value),
            }
        );
        eprintln!(
            "{}: Previous: {}",
            self.0,
            SpannedError {
                input: self.1.into(),
                span: prev,
                cause: None::<Infallible>,
            }
        );
    }
}
