#![allow(
    clippy::indexing_slicing,
    reason = "unwraps are allowed anywhere in tests"
)]

use std::{borrow::Cow, sync::Arc};

use assert_matches::assert_matches;

use crate::json;

use super::{unescape_str, Warning};

fn test_elem() -> json::Element<'static> {
    json::Element {
        id: 0.into(),
        path_node: Arc::new(json::PathNode::Root),
        span: json::parser::Span::default(),
        value: json::Value::Null,
    }
}

#[test]
fn should_unescape_empty_str() {
    const INPUT: &str = "";

    let elem = test_elem();
    let (string, warnings) = unescape_str(INPUT, &elem).into_parts();
    assert_matches!(string, Cow::Borrowed(""));
    assert!(warnings.is_empty(), "{:#?}", warnings.path_id_map());
}

#[test]
fn should_unescape_str_without_escapes() {
    const INPUT: &str = "ab";

    let elem = test_elem();
    let (string, warnings) = unescape_str(INPUT, &elem).into_parts();
    assert_matches!(string, Cow::Borrowed(INPUT));
    assert!(warnings.is_empty(), "{:#?}", warnings.path_id_map());
}

#[test]
fn should_unescape_str_with_forward_slash_escape() {
    const INPUT: &str = r"a\/b";

    let elem = test_elem();
    let (string, warnings) = unescape_str(INPUT, &elem).into_parts();
    let s = assert_matches!(
        string,
        Cow::Owned(s) => s
    );

    assert_eq!(s, "a/b");
    assert!(warnings.is_empty(), "{:#?}", warnings.path_id_map());
}

#[test]
fn should_unescape_str_with_many_escapes() {
    const INPUT: &str = r#"a\/\"b\""#;

    let elem = test_elem();
    let (string, warnings) = unescape_str(INPUT, &elem).into_parts();
    let s = assert_matches!(
        string,
        Cow::Owned(s) => s
    );

    assert_eq!(s, r#"a/"b""#);
    assert!(warnings.is_empty(), "{:#?}", warnings.path_id_map());
}

#[test]
fn should_fail_to_unescape_str_with_invalid_escape() {
    {
        const INPUT: &str = r"\a/c";

        let elem = test_elem();
        let (string, warnings) = unescape_str(INPUT, &elem).into_parts();
        let warnings = warnings.into_path_as_str_map();
        let warnings = &warnings["$"];

        assert_matches!(string, Cow::Borrowed(_));
        assert_matches!(warnings.as_slice(), [Warning::InvalidEscape(1)]);
    }

    {
        const INPUT: &str = r"a\c";

        let elem = test_elem();
        let (string, warnings) = unescape_str(INPUT, &elem).into_parts();
        let warnings = warnings.into_path_as_str_map();
        let warnings = &warnings["$"];

        assert_matches!(string, Cow::Borrowed(_));
        assert_matches!(warnings.as_slice(), [Warning::InvalidEscape(2)]);
    }

    {
        const INPUT: &str = r"a/c\";

        let elem = test_elem();
        let (string, warnings) = unescape_str(INPUT, &elem).into_parts();
        let warnings = warnings.into_path_as_str_map();
        let warnings = &warnings["$"];

        assert_matches!(string, Cow::Borrowed(_));
        assert_matches!(warnings.as_slice(), [Warning::UnexpectedEndOfString(3)]);
    }
}

#[test]
fn should_fail_to_unescape_str_with_control_char() {
    const INPUT: &str = "hello\u{0019}world";

    let elem = test_elem();
    let (string, warnings) = unescape_str(INPUT, &elem).into_parts();
    let warnings = warnings.into_path_as_str_map();
    let warnings = &warnings["$"];

    assert_matches!(string, Cow::Borrowed(_));
    assert_matches!(
        warnings.as_slice(),
        [Warning::ControlCharacterWhileParsingString(5)]
    );
}

#[test]
fn should_fail_to_unescape_raw_str_with_rust_unicode_literal_control_char() {
    const INPUT: &str = r"hello\u{0019}world";

    let elem = test_elem();
    let (string, warnings) = unescape_str(INPUT, &elem).into_parts();
    let warnings = warnings.into_path_as_str_map();
    let warnings = &warnings["$"];

    assert_matches!(string, Cow::Borrowed(_));
    assert_matches!(warnings.as_slice(), [Warning::InvalidEscape(10)]);
}

#[test]
fn should_fail_to_unescape_json_control_escape() {
    const INPUT: &str = r"hello\u0019world";

    let elem = test_elem();
    let (string, warnings) = unescape_str(INPUT, &elem).into_parts();
    let warnings = warnings.into_path_as_str_map();
    let warnings = &warnings["$"];

    assert_matches!(string, Cow::Borrowed(_));
    assert_matches!(
        warnings.as_slice(),
        [Warning::ControlCharacterWhileParsingString(10)]
    );
}

#[test]
fn should_unescape_unicode_literals() {
    const INPUT: &str = r"hello\u0020world\u0021";

    let elem = test_elem();
    let (string, warnings) = unescape_str(INPUT, &elem).into_parts();

    let s = assert_matches!(
        string,
        Cow::Owned(s) => s
    );
    assert_eq!(s, "hello world!");
    assert!(warnings.is_empty(), "{:#?}", warnings.path_id_map());
}

#[test]
fn should_unescape_utf_16_surrogate_pair() {
    // This test data is taken from the JSON RFC 8259 spec.
    //
    // * See: <https://datatracker.ietf.org/doc/html/rfc8259#section-7>
    const INPUT: &str = r"hello\uD834\uDD1Eworld";

    let elem = test_elem();
    let (string, warnings) = unescape_str(INPUT, &elem).into_parts();

    let s = assert_matches!(
        string,
        Cow::Owned(s) => s
    );
    assert_eq!(s, "hello\u{1D11E}world");
    assert!(warnings.is_empty(), "{:#?}", warnings.path_id_map());
}

#[test]
fn should_unescape_unicode_literal_followed_by_simple_escape() {
    const INPUT: &str = r"hello\u0020\/world\u0021";

    let elem = test_elem();
    let (string, warnings) = unescape_str(INPUT, &elem).into_parts();

    let s = assert_matches!(
        string,
        Cow::Owned(s) => s
    );
    assert_eq!(s, "hello /world!");
    assert!(warnings.is_empty(), "{:#?}", warnings.path_id_map());
}
