use std::path::Path;

use bstr::{ByteSlice, ByteVec};
use gix_filter::{eol, eol::AttributesDigest};

#[test]
fn with_binary_attribute_is_never_converted() {
    let mut buf = Vec::new();
    let changed = eol::convert_to_git(
        b"hi\r\nho",
        AttributesDigest::Binary,
        &mut buf,
        &mut no_call,
        Default::default(),
    )
    .expect("no error");
    assert!(!changed, "the user marked it as binary so it's never being touched");
}

#[test]
fn no_crlf_means_no_work() -> crate::Result {
    let mut buf = Vec::new();
    let changed = eol::convert_to_git(
        b"hi",
        AttributesDigest::TextCrlf,
        &mut buf,
        &mut no_call,
        Default::default(),
    )
    .expect("no error");
    assert!(!changed);

    let changed = eol::convert_to_git(
        b"hi",
        AttributesDigest::TextAutoCrlf,
        &mut buf,
        &mut no_object_in_index,
        Default::default(),
    )
    .expect("no error");
    assert!(!changed, "in auto-mode, the object is queried in the index as well.");
    Ok(())
}

#[test]
fn detected_as_binary() -> crate::Result {
    let mut buf = Vec::new();
    let changed = eol::convert_to_git(
        b"hi\0zero makes it binary",
        AttributesDigest::TextAuto,
        &mut buf,
        &mut no_call,
        Default::default(),
    )
    .expect("no error");
    assert!(
        !changed,
        "in auto-mode, we have a heuristic to see if the buffer is binary"
    );
    Ok(())
}

#[test]
fn fast_conversion_by_stripping_cr() -> crate::Result {
    let mut buf = Vec::new();
    let changed = eol::convert_to_git(
        b"a\r\nb\r\nc",
        AttributesDigest::TextCrlf,
        &mut buf,
        &mut no_call,
        Default::default(),
    )
    .expect("no error");
    assert!(changed);
    assert_eq!(buf.as_bstr(), "a\nb\nc", "here carriage returns can just be stripped");
    Ok(())
}

#[test]
fn slower_conversion_due_to_lone_cr() -> crate::Result {
    let mut buf = Vec::new();
    let changed = eol::convert_to_git(
        b"\r\ra\r\nb\r\nc",
        AttributesDigest::TextCrlf,
        &mut buf,
        &mut no_call,
        Default::default(),
    )
    .expect("no error");
    assert!(changed);
    assert_eq!(
        buf.as_bstr(),
        "\r\ra\nb\nc",
        "here carriage returns cannot be stripped but must be handled in pairs"
    );
    Ok(())
}

#[test]
fn crlf_in_index_prevents_conversion_to_lf() -> crate::Result {
    let mut buf = Vec::new();
    let mut called = false;
    let changed = eol::convert_to_git(
        b"eligible\n",
        AttributesDigest::TextAutoInput,
        &mut buf,
        &mut |buf| {
            called = true;
            buf.clear();
            buf.push_str("with CRLF\r\n");
            Ok(Some(()))
        },
        Default::default(),
    )
    .expect("no error");
    assert!(called, "in auto mode, the index is queried as well");
    assert!(
        !changed,
        "we saw the CRLF is present in the index, so it's unsafe to make changes"
    );
    Ok(())
}

#[test]
fn round_trip_check() -> crate::Result {
    let mut buf = Vec::new();
    for (input, expected) in [
        (&b"lone-nl\nhi\r\nho"[..], "LF would be replaced by CRLF in 'hello.txt'"),
        // despite trying, I was unable to get into the other branch
        (b"lone-cr\nhi\r\nho", "LF would be replaced by CRLF in 'hello.txt'"),
    ] {
        let err = eol::convert_to_git(
            input,
            AttributesDigest::TextCrlf,
            &mut buf,
            &mut no_call,
            eol::convert_to_git::Options {
                round_trip_check: Some(gix_filter::eol::convert_to_git::RoundTripCheck::Fail {
                    rela_path: Path::new("hello.txt"),
                }),
                config: Default::default(),
            },
        )
        .unwrap_err();
        assert_eq!(err.to_string(), expected);

        let changed = eol::convert_to_git(
            input,
            AttributesDigest::TextCrlf,
            &mut buf,
            &mut no_call,
            eol::convert_to_git::Options {
                round_trip_check: Some(gix_filter::eol::convert_to_git::RoundTripCheck::Warn {
                    rela_path: Path::new("hello.txt"),
                }),
                config: Default::default(),
            },
        )?;
        assert!(
            changed,
            "in warn mode, we will get a result even though it won't round-trip"
        );
    }
    Ok(())
}

#[allow(clippy::ptr_arg)]
fn no_call(_buf: &mut Vec<u8>) -> Result<Option<()>, Box<dyn std::error::Error + Send + Sync>> {
    unreachable!("index function will not be called")
}

#[allow(clippy::ptr_arg)]
fn no_object_in_index(_buf: &mut Vec<u8>) -> Result<Option<()>, Box<dyn std::error::Error + Send + Sync>> {
    Ok(None)
}
