use std::{env, fs};

use predicates::prelude::*;
use tempfile::{tempdir, NamedTempFile};

use super::*;

#[test]
fn aliases() {
    for subcmd in ["g", "get"] {
        for opt in ["-h", "--help"] {
            cmd("bite bugzilla attachment")
                .arg(subcmd)
                .arg(opt)
                .assert()
                .stdout(predicate::str::is_empty().not())
                .stderr("")
                .success();
        }
    }
}

#[test]
fn required_args() {
    // missing IDs
    cmd("bite bugzilla attachment get")
        .assert()
        .stdout("")
        .stderr(predicate::str::contains(
            "required arguments were not provided",
        ))
        .failure()
        .code(2);
}

#[tokio::test]
async fn invalid_ids() {
    let server = start_server().await;
    server
        .respond(200, TEST_DATA.join("attachment/get/single-plain-text.json"))
        .await;

    // string IDs only work with -i/--item-ids
    cmd("bite bugzilla attachment get abc")
        .assert()
        .stdout("")
        .stderr(predicate::str::diff("Error: invalid attachment ID: abc").trim())
        .failure()
        .code(1);

    for opt in ["-i", "--item-ids"] {
        cmd("bite bugzilla attachment get abc")
            .arg(opt)
            .assert()
            .stdout("")
            .stderr("")
            .success();
    }
}

#[tokio::test]
async fn nonexistent_bug() {
    let server = start_server().await;

    server
        .respond(404, TEST_DATA.join("errors/nonexistent-bug.json"))
        .await;

    for opt in ["-i", "--item-ids"] {
        cmd("bite bugzilla attachment get 1")
            .arg(opt)
            .assert()
            .stdout("")
            .stderr(predicate::str::diff("Error: bugzilla: Bug #1 does not exist.").trim())
            .failure();
    }
}

#[tokio::test]
async fn deleted_attachment() {
    let server = start_server().await;

    server
        .respond(200, TEST_DATA.join("attachment/get/deleted.json"))
        .await;

    cmd("bite bugzilla attachment get 21")
        .assert()
        .stdout("")
        .stderr(predicate::str::diff("Error: deleted attachment: 21").trim())
        .failure();
}

#[tokio::test]
async fn list() {
    let server = start_server().await;
    server
        .respond(
            200,
            TEST_DATA.join("attachment/get/single-without-data.json"),
        )
        .await;

    for opt in ["-l", "--list"] {
        // conflicts with -d/--dir and -o/--output
        for x in ["-d", "--dir", "-o", "--output"] {
            cmd("bite bugzilla attachment get 123")
                .arg(opt)
                .args([x, "arg"])
                .assert()
                .stdout("")
                .stderr(predicate::str::contains("cannot be used with"))
                .failure()
                .code(2);
        }

        // default output for single attachment
        cmd("bite bugzilla attachment get 123")
            .arg(opt)
            .assert()
            .stdout(predicate::str::diff("123: test.txt").trim())
            .stderr("")
            .success();

        // verbose output for single attachment
        cmd("bite bugzilla attachment get 123 -v")
            .arg(opt)
            .assert()
            .stdout(predicate::str::diff(indoc::indoc! {"
                123: test.txt
                  (text/plain) 8 B, created by person, 2024-02-19 08:35:02 UTC
            "}))
            .stderr("")
            .success();
    }

    server.reset().await;
    server
        .respond(
            200,
            TEST_DATA.join("attachment/get/multiple-without-data.json"),
        )
        .await;

    for opt in ["-l", "--list"] {
        // default output for multiple attachments
        cmd("bite bugzilla attachment get 123 124 125 126")
            .arg(opt)
            .assert()
            .stdout(predicate::str::diff(indoc::indoc! {"
                123: test file 1 (test1)
                124: test file 2 (test2.txt)
            "}))
            .stderr("")
            .success();

        // include outdated attachments
        for x in ["-O", "--outdated"] {
            cmd("bite bugzilla attachment get 123 124 125 126")
                .arg(opt)
                .arg(x)
                .assert()
                .stdout(predicate::str::diff(indoc::indoc! {"
                    123: test file 1 (test1)
                    124: test file 2 (test2.txt)
                    125: patch file (test.patch) (obsolete)
                    126: patch file (test.patch) (deleted)
                "}))
                .stderr("")
                .success();
        }

        // verbose output for multiple attachments
        cmd("bite bugzilla attachment get 123 124 125 126 -v")
            .arg(opt)
            .assert()
            .stdout(predicate::str::diff(indoc::indoc! {"
                123: test file 1 (test1)
                  (text/plain) 8 B, created by person, 2024-02-19 08:35:02 UTC
                124: test file 2 (test2.txt)
                  (text/plain) 8 B, created by person, 2024-02-19 08:35:02 UTC
            "}))
            .stderr("")
            .success();
    }
}

#[tokio::test]
async fn output_plain_text() {
    let server = start_server().await;
    server
        .respond(200, TEST_DATA.join("attachment/get/single-plain-text.json"))
        .await;
    let expected =
        fs::read_to_string(TEST_OUTPUT.join("attachment/get/single-plain-text")).unwrap();

    for opt in ["-o", "--output"] {
        // stdout target
        cmd("bite bugzilla attachment get 123")
            .args([opt, "-"])
            .assert()
            .stdout(predicate::str::diff(expected.clone()))
            .stderr("")
            .success();

        // file target
        let file = NamedTempFile::new().unwrap();
        let path = file.path().to_str().unwrap();
        cmd("bite bugzilla attachment get 123")
            .args([opt, path])
            .assert()
            .stdout("")
            .stderr("")
            .success();
        let content = fs::read_to_string(path).unwrap();
        assert_eq!(content, expected);
    }
}

#[tokio::test]
async fn save_single_with_plain_text() {
    let server = start_server().await;
    server
        .respond(200, TEST_DATA.join("attachment/get/single-plain-text.json"))
        .await;
    let expected =
        fs::read_to_string(TEST_OUTPUT.join("attachment/get/single-plain-text")).unwrap();

    let dir = tempdir().unwrap();
    let dir_path = dir.path().to_str().unwrap();
    // save files to the current working directory
    env::set_current_dir(dir_path).unwrap();

    cmd("bite bugzilla attachment get 123")
        .assert()
        .stdout(predicate::str::diff("Saving attachment: ./test.txt").trim())
        .stderr("")
        .success();

    // verify file content
    let file = dir.path().join("test.txt");
    let data = fs::read_to_string(file).unwrap();
    assert_eq!(&data, &expected);
}

#[tokio::test]
async fn save_single_existing_error() {
    let server = start_server().await;
    server
        .respond(200, TEST_DATA.join("attachment/get/single-plain-text.json"))
        .await;

    let dir = tempdir().unwrap();
    let dir_path = dir.path().to_str().unwrap();

    cmd("bite bugzilla attachment get 123")
        .args(["-d", dir_path])
        .assert()
        .stdout(predicate::str::diff(format!("Saving attachment: {dir_path}/test.txt")).trim())
        .stderr("")
        .success();

    // re-running causes a file existence failure
    cmd("bite bugzilla attachment get 123")
        .args(["-d", dir_path])
        .assert()
        .stdout("")
        .stderr(
            predicate::str::diff(format!("Error: file already exists: {dir_path}/test.txt")).trim(),
        )
        .failure();
}

#[tokio::test]
async fn single_bug_with_no_attachments() {
    let server = start_server().await;

    server
        .respond(
            200,
            TEST_DATA.join("attachment/get/bug-with-no-attachments.json"),
        )
        .await;

    for opt in ["-i", "--item-ids"] {
        cmd("bite bugzilla attachment get 12345")
            .arg(opt)
            .assert()
            .stdout("")
            .stderr("")
            .success();
    }
}

#[tokio::test]
async fn multiple_bugs_with_no_attachments() {
    let server = start_server().await;

    server
        .respond(
            200,
            TEST_DATA.join("attachment/get/bugs-with-no-attachments.json"),
        )
        .await;

    for opt in ["-i", "--item-ids"] {
        cmd("bite bugzilla attachment get 12345 23456 34567")
            .arg(opt)
            .assert()
            .stdout("")
            .stderr("")
            .success();
    }
}

#[tokio::test]
async fn save_multiple_with_plain_text() {
    let server = start_server().await;
    server
        .respond(
            200,
            TEST_DATA.join("attachment/get/bugs-with-attachments.json"),
        )
        .await;
    let expected =
        fs::read_to_string(TEST_OUTPUT.join("attachment/get/single-plain-text")).unwrap();

    let dir = tempdir().unwrap();
    let dir_path = dir.path().to_str().unwrap();
    // save files to the current working directory
    env::set_current_dir(dir_path).unwrap();

    let ids = ["12345", "23456", "34567"];
    cmd("bite bugzilla attachment get -i")
        .args(ids)
        .assert()
        .stdout(predicate::str::is_empty().not())
        .stderr("")
        .success();

    // verify file content
    for id in ids {
        let file = dir.path().join(format!("{id}/test.txt"));
        let data = fs::read_to_string(file).unwrap();
        assert_eq!(&data, &expected);
    }
}
