// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.

// spell-checker: ignore (encodings) lsbf msbf
// spell-checker: ignore autopad MFRGG MFRGGZDF abcdeabc baddecode CPNMUO

use uutests::{at_and_ucmd, new_ucmd};

#[test]
fn test_version() {
    new_ucmd!()
        .arg("--version")
        .succeeds()
        .no_stderr()
        .stdout_is(format!("basenc {}\n", uucore::crate_version!()));
}

#[test]
fn test_z85_not_padded_decode() {
    // The z85 crate deviates from the standard in some cases; we have to catch those
    new_ucmd!()
        .args(&["--z85", "-d"])
        .pipe_in("##########")
        .fails()
        .stderr_only("basenc: error: invalid input\n");
}

#[test]
fn test_z85_not_padded_encode() {
    // The z85 crate deviates from the standard in some cases; we have to catch those
    new_ucmd!()
        .args(&["--z85"])
        .pipe_in("123")
        .fails()
        .stderr_only("basenc: error: invalid input (length must be multiple of 4 characters)\n");
}

#[test]
fn test_invalid_input() {
    let cmd = new_ucmd!().args(&["--base32", "."]).fails();
    if cfg!(windows) {
        cmd.stderr_only("basenc: .: Permission denied\n");
    } else if std::env::var("UUTESTS_WASM_RUNNER").is_ok() {
        // wasi-libc may report a different error string than the host libc
        cmd.stderr_contains("basenc: read error:");
    } else {
        cmd.stderr_only("basenc: read error: Is a directory\n");
    }
}

#[test]
fn test_base64() {
    new_ucmd!()
        .arg("--base64")
        .pipe_in("to>be?")
        .succeeds()
        .stdout_only("dG8+YmU/\n");
}

#[test]
fn test_base64_decode() {
    new_ucmd!()
        .args(&["--base64", "-d"])
        .pipe_in("dG8+YmU/")
        .succeeds()
        .stdout_only("to>be?");
}

#[test]
fn test_base64url() {
    new_ucmd!()
        .arg("--base64url")
        .pipe_in("to>be?")
        .succeeds()
        .stdout_only("dG8-YmU_\n");
}

#[test]
fn test_base64url_decode() {
    new_ucmd!()
        .args(&["--base64url", "-d"])
        .pipe_in("dG8-YmU_")
        .succeeds()
        .stdout_only("to>be?");
}

#[test]
fn test_base32() {
    new_ucmd!()
        .arg("--base32")
        .pipe_in("nice>base?")
        .succeeds()
        .stdout_only("NZUWGZJ6MJQXGZJ7\n"); // spell-checker:disable-line
}

#[test]
fn test_base32_decode() {
    new_ucmd!()
        .args(&["--base32", "-d"])
        .pipe_in("NZUWGZJ6MJQXGZJ7") // spell-checker:disable-line
        .succeeds()
        .stdout_only("nice>base?");
}

#[test]
fn test_base32hex() {
    new_ucmd!()
        .arg("--base32hex")
        .pipe_in("nice>base?")
        .succeeds()
        .stdout_only("DPKM6P9UC9GN6P9V\n"); // spell-checker:disable-line
}

#[test]
fn test_base32hex_decode() {
    new_ucmd!()
        .args(&["--base32hex", "-d"])
        .pipe_in("DPKM6P9UC9GN6P9V") // spell-checker:disable-line
        .succeeds()
        .stdout_only("nice>base?");
}

#[test]
fn test_base32_autopad_short_quantum() {
    new_ucmd!()
        .args(&["--base32", "--decode"])
        .pipe_in("MFRGG")
        .succeeds()
        .stdout_only("abc");
}

#[test]
fn test_base32_autopad_multiline_stream() {
    new_ucmd!()
        .args(&["--base32", "--decode"])
        .pipe_in("MFRGGZDF\nMFRGG")
        .succeeds()
        .stdout_only("abcdeabc");
}

#[test]
fn test_base32_baddecode_keeps_prefix() {
    new_ucmd!()
        .args(&["--base32", "--decode"])
        .pipe_in("MFRGGZDF=")
        .fails()
        .stdout_is("abcde")
        .stderr_is("basenc: error: invalid input\n");
}

#[test]
fn test_base32hex_autopad_short_quantum() {
    new_ucmd!()
        .args(&["--base32hex", "--decode"])
        .pipe_in("C5H66")
        .succeeds()
        .stdout_only("abc");
}

#[test]
fn test_base32hex_rejects_trailing_garbage() {
    new_ucmd!()
        .args(&["--base32hex", "-d"])
        .pipe_in("VNC0FKD5W")
        .fails()
        .stdout_is_bytes(b"\xFD\xD8\x07\xD1\xA5")
        .stderr_is("basenc: error: invalid input\n");
}

#[test]
fn test_base32hex_truncated_block_keeps_prefix() {
    new_ucmd!()
        .args(&["--base32hex", "-d"])
        .pipe_in("CPNMUO")
        .fails()
        .stdout_is_bytes(b"foo")
        .stderr_is("basenc: error: invalid input\n");
}

#[test]
fn test_base16() {
    new_ucmd!()
        .arg("--base16")
        .pipe_in("Hello, World!")
        .succeeds()
        .stdout_only("48656C6C6F2C20576F726C6421\n");
}

#[test]
fn test_base16_decode() {
    new_ucmd!()
        .args(&["--base16", "-d"])
        .pipe_in("48656C6C6F2C20576F726C6421")
        .succeeds()
        .stdout_only("Hello, World!");
}

#[test]
fn test_base16_decode_lowercase() {
    new_ucmd!()
        .args(&["--base16", "-d"])
        .pipe_in("48656c6c6f2c20576f726c6421")
        .succeeds()
        .stdout_only("Hello, World!");
}

#[test]
fn test_base16_decode_and_ignore_garbage_lowercase() {
    new_ucmd!()
        .args(&["--base16", "-d", "-i"])
        .pipe_in("48656c6c6f2c20576f726c6421")
        .succeeds()
        .stdout_only("Hello, World!");
}

#[test]
fn test_base2msbf() {
    new_ucmd!()
        .arg("--base2msbf")
        .pipe_in("msbf")
        .succeeds()
        .stdout_only("01101101011100110110001001100110\n");
}

#[test]
fn test_base2msbf_decode() {
    new_ucmd!()
        .args(&["--base2msbf", "-d"])
        .pipe_in("01101101011100110110001001100110")
        .succeeds()
        .stdout_only("msbf");
}

#[test]
fn test_base2lsbf() {
    new_ucmd!()
        .arg("--base2lsbf")
        .pipe_in("lsbf")
        .succeeds()
        .stdout_only("00110110110011100100011001100110\n");
}

#[test]
fn test_base2lsbf_decode() {
    new_ucmd!()
        .args(&["--base2lsbf", "-d"])
        .pipe_in("00110110110011100100011001100110")
        .succeeds()
        .stdout_only("lsbf");
}

#[test]
fn test_z85_decode() {
    new_ucmd!()
        .args(&["--z85", "-d"])
        .pipe_in("nm=QNz.92jz/PV8")
        .succeeds()
        .stdout_only("Hello, World");
}

#[test]
fn test_base58() {
    new_ucmd!()
        .arg("--base58")
        .pipe_in("Hello, World!")
        .succeeds()
        .stdout_only("72k1xXWG59fYdzSNoA\n");
}

#[test]
fn test_base58_decode() {
    new_ucmd!()
        .args(&["--base58", "-d"])
        .pipe_in("72k1xXWG59fYdzSNoA")
        .succeeds()
        .stdout_only("Hello, World!");
}

#[test]
fn test_base58_large_file_no_chunking() {
    // Regression test: base58 must process entire input as one big integer,
    // not in 1024-byte chunks. This test ensures files >1024 bytes work correctly.
    let (at, mut ucmd) = at_and_ucmd!();
    let filename = "large_file.txt";

    // spell-checker:disable
    let input = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ".repeat(50);
    // spell-checker:enable
    at.write(filename, &input);

    let result = ucmd.arg("--base58").arg(filename).succeeds();
    let encoded = result.stdout_str();

    // Verify the output ends with the expected suffix (matches GNU basenc output)
    // spell-checker:disable
    assert!(
        encoded
            .trim_end()
            .ends_with("ZNRRacEnhrY83ZEYkpwWVZNFK5DFRasr\nw693NsNGtiQ9fYAj")
    );
    // spell-checker:enable
}

#[test]
fn test_choose_last_encoding_base64() {
    new_ucmd!()
        .args(&[
            "--base2msbf",
            "--base2lsbf",
            "--base64url",
            "--base32hex",
            "--base32",
            "--base16",
            "--z85",
            "--base64",
        ])
        .pipe_in("Hello, World!")
        .succeeds()
        .stdout_only("SGVsbG8sIFdvcmxkIQ==\n"); // spell-checker:disable-line
}

#[test]
fn test_choose_last_encoding_base2lsbf() {
    new_ucmd!()
        .args(&[
            "--base64url",
            "--base16",
            "--base2msbf",
            "--base32",
            "--base64",
            "--z85",
            "--base32hex",
            "--base2lsbf",
        ])
        .pipe_in("lsbf")
        .succeeds()
        .stdout_only("00110110110011100100011001100110\n");
}

#[test]
fn test_choose_last_encoding_base58() {
    new_ucmd!()
        .args(&["--base64", "--base32", "--base16", "--z85", "--base58"])
        .pipe_in("Hello!")
        .succeeds()
        .stdout_only("d3yC1LKr\n");
}

#[test]
fn test_base32_decode_repeated() {
    new_ucmd!()
        .args(&[
            "--ignore",
            "--wrap=80",
            "--base32hex",
            "--z85",
            "--ignore",
            "--decode",
            "--z85",
            "--base32",
            "-w",
            "10",
        ])
        .pipe_in("NZUWGZJ6MJQXGZJ7") // spell-checker:disable-line
        .succeeds()
        .stdout_only("nice>base?");
}

// The restriction that input length has to be divisible by 4 only applies to data being encoded with Z85, not to the
// decoding of Z85-encoded data
#[test]
fn test_z85_length_check() {
    new_ucmd!()
        .args(&["--decode", "--z85"])
        // Input has length 10, not divisible by 4
        // spell-checker:disable-next-line
        .pipe_in("f!$Kwh8WxM")
        .succeeds()
        .stdout_only("12345678");
}

#[test]
fn test_file() {
    let (at, mut ucmd) = at_and_ucmd!();
    let filename = "file";

    at.write(filename, "foo");

    ucmd.arg(filename)
        .arg("--base64")
        .succeeds()
        .stdout_is("Zm9v\n");
}

#[test]
#[cfg(target_os = "linux")]
#[cfg_attr(wasi_runner, ignore = "WASI: argv/filenames must be valid UTF-8")]
fn test_file_with_non_utf8_name() {
    use std::os::unix::ffi::OsStringExt;
    let (at, mut ucmd) = at_and_ucmd!();

    let filename = std::ffi::OsString::from_vec(vec![0xFF, 0xFE]);
    std::fs::write(at.plus(&filename), b"foo").unwrap();

    ucmd.arg(filename)
        .arg("--base64")
        .succeeds()
        .stdout_is("Zm9v\n");
}
