#![allow(
    missing_docs,
    clippy::expect_used,
    clippy::needless_raw_string_hashes,
    clippy::panic,
    clippy::too_many_lines,
    clippy::unwrap_used
)]

mod mock_support;

use std::io::Cursor;

use axum::http::{Method, StatusCode};
use figshare_rs::{
    ArticleId, ArticleMetadata, ArticleOrder, ArticleQuery, DefinedType, Doi, FigshareError,
    FileReplacePolicy, UploadSpec,
};
use serde_json::json;
use tempfile::tempdir;

use crate::mock_support::{MockFigshareServer, QueuedResponse};

fn public_article_json(server: &MockFigshareServer, id: u64, version: u64) -> serde_json::Value {
    json!({
        "id": id,
        "title": format!("Article {id}"),
        "doi": format!("10.6084/m9.figshare.{id}"),
        "defined_type": 3,
        "is_public": true,
        "version": version,
        "url_public_api": server.api_url(&format!("articles/{id}")),
        "files": [{
            "id": id * 10,
            "name": "artifact.bin",
            "size": 5,
            "download_url": server.raw_url(&format!("files/{id}/artifact.bin"))
        }]
    })
}

fn private_article_json(
    server: &MockFigshareServer,
    id: u64,
    is_public: bool,
) -> serde_json::Value {
    json!({
        "id": id,
        "title": format!("Private {id}"),
        "defined_type": "dataset",
        "status": if is_public { "public" } else { "draft" },
        "is_public": is_public,
        "url_private_api": server.api_url(&format!("account/articles/{id}")),
        "files": []
    })
}

fn private_file_json(
    server: &MockFigshareServer,
    article_id: u64,
    file_id: u64,
) -> serde_json::Value {
    json!({
        "id": file_id,
        "name": "artifact.bin",
        "size": 6,
        "status": "created",
        "is_link_only": false,
        "download_url": server.raw_url(&format!("files/{article_id}/artifact.bin")),
        "upload_url": server.raw_url(&format!("upload/token-{file_id}")),
        "upload_token": format!("token-{file_id}"),
        "supplied_md5": "e80b5017098950fc58aad83c8c14978e"
    })
}

fn public_file_json(
    server: &MockFigshareServer,
    article_id: u64,
    file_id: u64,
) -> serde_json::Value {
    json!({
        "id": file_id,
        "name": "artifact.bin",
        "size": 5,
        "download_url": server.raw_url(&format!("files/{article_id}/artifact.bin"))
    })
}

fn article_metadata() -> ArticleMetadata {
    ArticleMetadata::builder()
        .title("Example dataset")
        .defined_type(DefinedType::Dataset)
        .description("Example description")
        .author_named("Doe, Jane")
        .tag("data")
        .license_id(1)
        .build()
        .expect("valid metadata")
}

#[tokio::test]
async fn public_article_methods_use_expected_routes_and_payloads() {
    let server = MockFigshareServer::start().await;
    let client = server.anonymous_client();

    server.enqueue_json(
        Method::GET,
        "/v2/articles",
        StatusCode::OK,
        json!([{
            "id": 1,
            "title": "One",
            "defined_type": 3
        }]),
    );
    server.enqueue_json(
        Method::POST,
        "/v2/articles/search",
        StatusCode::OK,
        json!([public_article_json(&server, 2, 1)]),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/articles/2",
        StatusCode::OK,
        public_article_json(&server, 2, 1),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/articles/2/versions",
        StatusCode::OK,
        json!([
            { "version": 1, "url": server.api_url("articles/2/versions/1") },
            { "version": 3, "url": server.api_url("articles/2/versions/3") }
        ]),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/articles/2/versions/3",
        StatusCode::OK,
        public_article_json(&server, 2, 3),
    );

    let listed = client
        .list_public_articles(
            &ArticleQuery::builder()
                .order(ArticleOrder::PublishedDate)
                .page(2)
                .page_size(25)
                .item_type(DefinedType::Dataset)
                .build(),
        )
        .await
        .expect("list public");
    assert_eq!(listed[0].id, ArticleId(1));

    let searched = client
        .search_public_articles(
            &ArticleQuery::builder()
                .item_type(DefinedType::Dataset)
                .limit(10)
                .build(),
        )
        .await
        .expect("search public");
    assert_eq!(searched[0].id, ArticleId(2));

    let latest = client
        .resolve_latest_public_article(ArticleId(2))
        .await
        .expect("latest article");
    assert_eq!(latest.version_number(), Some(3));

    let requests = server.requests();
    assert!(requests[0]
        .query
        .as_deref()
        .unwrap_or_default()
        .contains("item_type=3"));
    let search_body: serde_json::Value = serde_json::from_slice(&requests[1].body).unwrap();
    assert_eq!(search_body["item_type"], 3);
    assert_eq!(search_body["limit"], 10);
}

#[tokio::test]
async fn doi_lookup_uses_exact_public_search() {
    let server = MockFigshareServer::start().await;
    let client = server.anonymous_client();

    server.enqueue_json(
        Method::GET,
        "/v2/articles",
        StatusCode::OK,
        json!([
            {
                "id": 10,
                "title": "Wrong",
                "doi": "10.6084/m9.figshare.other"
            },
            public_article_json(&server, 11, 1)
        ]),
    );

    let doi = Doi::new("10.6084/m9.figshare.11").unwrap();
    let article = client
        .get_public_article_by_doi(&doi)
        .await
        .expect("article by doi");
    assert_eq!(article.id, ArticleId(11));

    let requests = server.requests();
    assert!(requests[0]
        .query
        .as_deref()
        .unwrap_or_default()
        .contains("doi=10.6084%2Fm9.figshare.11"));
}

#[tokio::test]
async fn private_article_methods_follow_location_and_send_auth_header() {
    let server = MockFigshareServer::start().await;
    let client = server.client();

    server.enqueue_json(
        Method::POST,
        "/v2/account/articles",
        StatusCode::CREATED,
        json!({ "location": server.api_url("account/articles/5") }),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/5",
        StatusCode::OK,
        private_article_json(&server, 5, false),
    );
    server.enqueue_text(
        Method::PUT,
        "/v2/account/articles/5",
        StatusCode::RESET_CONTENT,
        "",
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/5",
        StatusCode::OK,
        private_article_json(&server, 5, false),
    );
    server.enqueue_json(
        Method::POST,
        "/v2/account/articles/5/reserve_doi",
        StatusCode::OK,
        json!({ "doi": "10.6084/m9.figshare.5" }),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/categories",
        StatusCode::OK,
        json!([{
            "id": 3,
            "title": "Allowed category"
        }]),
    );
    server.enqueue_text(
        Method::DELETE,
        "/v2/account/articles/5",
        StatusCode::NO_CONTENT,
        "",
    );

    let article = client
        .create_article(&article_metadata())
        .await
        .expect("create article");
    assert_eq!(article.id, ArticleId(5));
    client
        .update_article(ArticleId(5), &article_metadata())
        .await
        .expect("update article");
    let doi = client.reserve_doi(ArticleId(5)).await.expect("reserve doi");
    assert_eq!(doi.as_str(), "10.6084/m9.figshare.5");
    let categories = client
        .list_account_categories()
        .await
        .expect("list account categories");
    assert_eq!(categories[0].id, figshare_rs::CategoryId(3));
    client
        .delete_article(ArticleId(5))
        .await
        .expect("delete article");

    let requests = server.requests();
    assert_eq!(
        requests[0].headers.get("authorization").map(String::as_str),
        Some("token test-token")
    );
    let create_body: serde_json::Value = serde_json::from_slice(&requests[0].body).unwrap();
    assert_eq!(create_body["title"], "Example dataset");
    assert_eq!(create_body["defined_type"], "dataset");
}

#[tokio::test]
async fn publish_waits_for_visibility_changes() {
    let server = MockFigshareServer::start().await;
    let client = server.client();

    server.enqueue_json(
        Method::POST,
        "/v2/account/articles/6/publish",
        StatusCode::CREATED,
        json!({ "location": server.api_url("articles/6") }),
    );
    server.enqueue_text(
        Method::GET,
        "/v2/articles/6",
        StatusCode::NOT_FOUND,
        "missing",
    );
    server.enqueue_json(
        Method::GET,
        "/v2/articles/6",
        StatusCode::OK,
        public_article_json(&server, 6, 1),
    );
    let public = client
        .publish_article(ArticleId(6))
        .await
        .expect("publish article");
    assert!(public.is_public_article());
}

#[tokio::test]
async fn file_management_endpoints_cover_listing_reading_deleting_and_link_files() {
    let server = MockFigshareServer::start().await;
    let client = server.client();

    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/8/files",
        StatusCode::OK,
        json!([private_file_json(&server, 8, 80)]),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/8/files/80",
        StatusCode::OK,
        private_file_json(&server, 8, 80),
    );
    server.enqueue_text(
        Method::DELETE,
        "/v2/account/articles/8/files/80",
        StatusCode::NO_CONTENT,
        "",
    );
    server.enqueue_json(
        Method::POST,
        "/v2/account/articles/8/files",
        StatusCode::CREATED,
        json!({ "location": server.api_url("account/articles/8/files/81") }),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/8/files/81",
        StatusCode::OK,
        json!({
            "id": 81,
            "name": "link.bin",
            "size": 0,
            "is_link_only": true,
            "download_url": "https://example.com/external.bin"
        }),
    );

    let files = client.list_files(ArticleId(8)).await.expect("list files");
    assert_eq!(files[0].id.0, 80);
    client
        .get_file(ArticleId(8), figshare_rs::FileId(80))
        .await
        .expect("get file");
    client
        .delete_file(ArticleId(8), figshare_rs::FileId(80))
        .await
        .expect("delete file");
    let linked = client
        .initiate_link_file(ArticleId(8), "https://example.com/external.bin")
        .await
        .expect("link file");
    assert_eq!(linked.id.0, 81);

    let requests = server.requests();
    let link_body: serde_json::Value = serde_json::from_slice(&requests[3].body).unwrap();
    assert_eq!(link_body["link"], "https://example.com/external.bin");
}

#[tokio::test]
async fn upload_path_and_reader_use_upload_service_routes() {
    let server = MockFigshareServer::start().await;
    let client = server.client();
    let dir = tempdir().expect("tempdir");
    let path = dir.path().join("artifact.bin");
    std::fs::write(&path, b"abcdef").expect("write temp file");

    server.enqueue_json(
        Method::POST,
        "/v2/account/articles/9/files",
        StatusCode::CREATED,
        json!({ "location": server.api_url("account/articles/9/files/90") }),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/9/files/90",
        StatusCode::OK,
        private_file_json(&server, 9, 90),
    );
    server.enqueue_json(
        Method::GET,
        "/upload/token-90",
        StatusCode::OK,
        json!({
            "token": "token-90",
            "name": "artifact.bin",
            "size": 6,
            "md5": "e80b5017098950fc58aad83c8c14978e",
            "status": "PENDING",
            "parts": [
                { "partNo": 1, "startOffset": 0, "endOffset": 2, "status": "PENDING", "locked": false },
                { "partNo": 2, "startOffset": 3, "endOffset": 5, "status": "PENDING", "locked": false }
            ]
        }),
    );
    server.enqueue_text(Method::PUT, "/upload/token-90/1", StatusCode::OK, "");
    server.enqueue_text(Method::PUT, "/upload/token-90/2", StatusCode::OK, "");
    server.enqueue_text(
        Method::POST,
        "/v2/account/articles/9/files/90",
        StatusCode::ACCEPTED,
        "",
    );
    server.enqueue_json(
        Method::GET,
        "/upload/token-90",
        StatusCode::OK,
        json!({
            "token": "token-90",
            "name": "artifact.bin",
            "size": 6,
            "status": "COMPLETED",
            "parts": []
        }),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/9/files/90",
        StatusCode::OK,
        private_file_json(&server, 9, 90),
    );

    let uploaded = client
        .upload_path(ArticleId(9), &path)
        .await
        .expect("upload path");
    assert_eq!(uploaded.id.0, 90);

    server.enqueue_json(
        Method::POST,
        "/v2/account/articles/9/files",
        StatusCode::CREATED,
        json!({ "location": server.api_url("account/articles/9/files/91") }),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/9/files/91",
        StatusCode::OK,
        json!({
            "id": 91,
            "name": "reader.bin",
            "size": 5,
            "status": "created",
            "is_link_only": false,
            "upload_url": server.raw_url("upload/token-91")
        }),
    );
    server.enqueue_json(
        Method::GET,
        "/upload/token-91",
        StatusCode::OK,
        json!({
            "token": "token-91",
            "name": "reader.bin",
            "size": 5,
            "status": "PENDING",
            "parts": [
                { "partNo": 1, "startOffset": 0, "endOffset": 4, "status": "PENDING", "locked": false }
            ]
        }),
    );
    server.enqueue_text(Method::PUT, "/upload/token-91/1", StatusCode::OK, "");
    server.enqueue_text(
        Method::POST,
        "/v2/account/articles/9/files/91",
        StatusCode::ACCEPTED,
        "",
    );
    server.enqueue_json(
        Method::GET,
        "/upload/token-91",
        StatusCode::OK,
        json!({
            "token": "token-91",
            "name": "reader.bin",
            "size": 5,
            "status": "COMPLETED",
            "parts": []
        }),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/9/files/91",
        StatusCode::OK,
        json!({
            "id": 91,
            "name": "reader.bin",
            "size": 5,
            "status": "available",
            "is_link_only": false
        }),
    );

    let uploaded_reader = client
        .upload_reader(
            ArticleId(9),
            "reader.bin",
            Cursor::new(b"12345".to_vec()),
            5,
        )
        .await
        .expect("upload reader");
    assert_eq!(uploaded_reader.id.0, 91);

    let requests = server.requests();
    let part_bodies: Vec<_> = requests
        .iter()
        .filter(|request| request.path.starts_with("/upload/token-"))
        .filter(|request| request.method == Method::PUT)
        .map(|request| request.body.clone())
        .collect();
    assert_eq!(
        part_bodies,
        vec![b"abc".to_vec(), b"def".to_vec(), b"12345".to_vec()]
    );
}

#[tokio::test]
async fn download_helpers_use_dedicated_file_list_endpoints() {
    let server = MockFigshareServer::start().await;
    let public_client = server.anonymous_client();
    let private_client = server.client();
    let dir = tempdir().expect("tempdir");

    server.enqueue_json(
        Method::GET,
        "/v2/articles/12",
        StatusCode::OK,
        json!({
            "id": 12,
            "title": "Article 12",
            "doi": "10.6084/m9.figshare.12",
            "defined_type": 3,
            "is_public": true,
            "version": 1,
            "url_public_api": server.api_url("articles/12"),
            "files": []
        }),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/articles/12/versions/1/files",
        StatusCode::OK,
        json!([public_file_json(&server, 12, 120)]),
    );
    server.enqueue(
        Method::GET,
        "/files/12/artifact.bin",
        QueuedResponse::bytes(StatusCode::OK, vec![], b"hello".to_vec()),
    );

    let public_path = dir.path().join("public.bin");
    let public_download = public_client
        .download_public_article_file_by_name_to_path(
            ArticleId(12),
            "artifact.bin",
            false,
            &public_path,
        )
        .await
        .expect("public download");
    assert_eq!(public_download.bytes_written, 5);
    assert_eq!(std::fs::read(&public_path).unwrap(), b"hello");

    server.enqueue_json(
        Method::GET,
        "/v2/articles",
        StatusCode::OK,
        json!([{
            "id": 13,
            "title": "Article 13",
            "doi": "10.6084/m9.figshare.13",
            "defined_type": 3,
            "is_public": true,
            "version": 1,
            "url_public_api": server.api_url("articles/13"),
            "files": []
        }]),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/articles/13/versions/1/files",
        StatusCode::OK,
        json!([public_file_json(&server, 13, 130)]),
    );
    server.enqueue(
        Method::GET,
        "/files/13/artifact.bin",
        QueuedResponse::bytes(StatusCode::OK, vec![], b"world".to_vec()),
    );

    let doi_path = dir.path().join("doi.bin");
    let doi = Doi::new("10.6084/m9.figshare.13").unwrap();
    private_client
        .download_article_file_by_doi_to_path(&doi, "artifact.bin", false, &doi_path)
        .await
        .expect("doi download");
    assert_eq!(std::fs::read(&doi_path).unwrap(), b"world");

    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/14/files",
        StatusCode::OK,
        json!([{
            "id": 140,
            "name": "artifact.bin",
            "size": 5,
            "download_url": server.raw_url("files/140")
        }]),
    );
    server.enqueue(
        Method::GET,
        "/files/140",
        QueuedResponse::bytes(StatusCode::OK, vec![], b"owner".to_vec()),
    );

    let own_path = dir.path().join("own.bin");
    private_client
        .download_own_article_file_by_name_to_path(ArticleId(14), "artifact.bin", &own_path)
        .await
        .expect("own download");
    assert_eq!(std::fs::read(&own_path).unwrap(), b"owner");

    let requests = server.requests();
    let private_download = requests
        .iter()
        .find(|request| request.path == "/files/140")
        .expect("private download request");
    assert_eq!(private_download.query.as_deref(), Some("token=test-token"));
    assert!(requests
        .iter()
        .any(|request| request.path == "/v2/articles/12/versions/1/files"));
    assert!(requests
        .iter()
        .any(|request| request.path == "/v2/articles/13/versions/1/files"));
    assert!(requests
        .iter()
        .any(|request| request.path == "/v2/account/articles/14/files"));
}

#[tokio::test]
async fn workflow_helpers_cover_reconcile_and_publish() {
    let server = MockFigshareServer::start().await;
    let client = server.client();
    let dir = tempdir().expect("tempdir");
    let path = dir.path().join("artifact.bin");
    std::fs::write(&path, b"abcdef").expect("write temp file");

    server.enqueue_text(
        Method::PUT,
        "/v2/account/articles/20",
        StatusCode::RESET_CONTENT,
        "",
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/20",
        StatusCode::OK,
        private_article_json(&server, 20, false),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/20/files",
        StatusCode::OK,
        json!([]),
    );
    server.enqueue_json(
        Method::POST,
        "/v2/account/articles/20/files",
        StatusCode::CREATED,
        json!({ "location": server.api_url("account/articles/20/files/200") }),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/20/files/200",
        StatusCode::OK,
        private_file_json(&server, 20, 200),
    );
    server.enqueue_json(
        Method::GET,
        "/upload/token-200",
        StatusCode::OK,
        json!({
            "token": "token-200",
            "name": "artifact.bin",
            "size": 6,
            "status": "PENDING",
            "parts": [
                { "partNo": 1, "startOffset": 0, "endOffset": 5, "status": "PENDING", "locked": false }
            ]
        }),
    );
    server.enqueue_text(Method::PUT, "/upload/token-200/1", StatusCode::OK, "");
    server.enqueue_text(
        Method::POST,
        "/v2/account/articles/20/files/200",
        StatusCode::ACCEPTED,
        "",
    );
    server.enqueue_json(
        Method::GET,
        "/upload/token-200",
        StatusCode::OK,
        json!({
            "token": "token-200",
            "name": "artifact.bin",
            "size": 6,
            "status": "COMPLETED",
            "parts": []
        }),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/20/files/200",
        StatusCode::OK,
        private_file_json(&server, 20, 200),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/20/files",
        StatusCode::OK,
        json!([private_file_json(&server, 20, 200)]),
    );
    server.enqueue_json(
        Method::POST,
        "/v2/account/articles/20/publish",
        StatusCode::CREATED,
        json!({ "location": server.api_url("articles/20") }),
    );
    server.enqueue_text(
        Method::GET,
        "/v2/articles/20",
        StatusCode::NOT_FOUND,
        "missing",
    );
    server.enqueue_json(
        Method::GET,
        "/v2/articles/20",
        StatusCode::OK,
        public_article_json(&server, 20, 1),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/20",
        StatusCode::OK,
        private_article_json(&server, 20, false),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/20",
        StatusCode::OK,
        private_article_json(&server, 20, true),
    );

    let published = client
        .publish_existing_article_with_policy(
            ArticleId(20),
            &article_metadata(),
            FileReplacePolicy::ReplaceAll,
            vec![UploadSpec::from_path(&path).unwrap()],
        )
        .await
        .expect("publish workflow");
    assert!(published.article.is_public_article());
    assert_eq!(published.public_article.id, ArticleId(20));
}

#[tokio::test]
async fn reconcile_files_rejects_conflicts_for_keep_existing_policy() {
    let server = MockFigshareServer::start().await;
    let client = server.client();
    let article = figshare_rs::Article {
        id: ArticleId(21),
        title: "Example".into(),
        ..serde_json::from_value(json!({
            "id": 21,
            "title": "Example"
        }))
        .unwrap()
    };
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/21/files",
        StatusCode::OK,
        json!([{
            "id": 210,
            "name": "artifact.bin",
            "size": 1
        }]),
    );

    let error = client
        .reconcile_files(
            &article,
            FileReplacePolicy::KeepExistingAndAdd,
            vec![UploadSpec::from_reader(
                "artifact.bin",
                Cursor::new(vec![1]),
                1,
            )],
        )
        .await
        .expect_err("conflict expected");
    assert!(matches!(
        error,
        FigshareError::ConflictingDraftFile { filename } if filename == "artifact.bin"
    ));
}

#[tokio::test]
async fn reconcile_files_does_not_delete_existing_files_before_successful_replacement() {
    let server = MockFigshareServer::start().await;
    let client = server.client();
    let dir = tempdir().expect("tempdir");
    let path = dir.path().join("artifact.bin");
    std::fs::write(&path, b"abcdef").expect("write temp file");
    let article = figshare_rs::Article {
        id: ArticleId(30),
        title: "Example".into(),
        ..serde_json::from_value(json!({
            "id": 30,
            "title": "Example"
        }))
        .unwrap()
    };

    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/30/files",
        StatusCode::OK,
        json!([{
            "id": 300,
            "name": "existing.bin",
            "size": 1
        }]),
    );
    server.enqueue_json(
        Method::POST,
        "/v2/account/articles/30/files",
        StatusCode::CREATED,
        json!({ "location": server.api_url("account/articles/30/files/301") }),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/30/files/301",
        StatusCode::OK,
        private_file_json(&server, 30, 301),
    );
    server.enqueue_json(
        Method::GET,
        "/upload/token-301",
        StatusCode::OK,
        json!({
            "token": "token-301",
            "name": "artifact.bin",
            "size": 6,
            "status": "PENDING",
            "parts": [
                { "partNo": 1, "startOffset": 0, "endOffset": 5, "status": "PENDING", "locked": false }
            ]
        }),
    );
    server.enqueue_text(
        Method::PUT,
        "/upload/token-301/1",
        StatusCode::INTERNAL_SERVER_ERROR,
        "upload failed",
    );
    server.enqueue_text(
        Method::DELETE,
        "/v2/account/articles/30/files/301",
        StatusCode::NO_CONTENT,
        "",
    );

    let error = client
        .reconcile_files(
            &article,
            FileReplacePolicy::ReplaceAll,
            vec![UploadSpec::from_path(&path).unwrap()],
        )
        .await
        .expect_err("upload failure expected");
    assert!(matches!(error, FigshareError::Http { .. }));

    let requests = server.requests();
    assert!(!requests.iter().any(|request| {
        request.method == Method::DELETE && request.path == "/v2/account/articles/30/files/300"
    }));
    assert!(requests.iter().any(|request| {
        request.method == Method::DELETE && request.path == "/v2/account/articles/30/files/301"
    }));
}

#[tokio::test]
async fn create_and_publish_article_deletes_new_draft_after_upload_failure() {
    let server = MockFigshareServer::start().await;
    let client = server.client();
    let dir = tempdir().expect("tempdir");
    let path = dir.path().join("artifact.bin");
    std::fs::write(&path, b"abcdef").expect("write temp file");

    server.enqueue_json(
        Method::POST,
        "/v2/account/articles",
        StatusCode::CREATED,
        json!({ "location": server.api_url("account/articles/40") }),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/40",
        StatusCode::OK,
        private_article_json(&server, 40, false),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/40/files",
        StatusCode::OK,
        json!([]),
    );
    server.enqueue_json(
        Method::POST,
        "/v2/account/articles/40/files",
        StatusCode::CREATED,
        json!({ "location": server.api_url("account/articles/40/files/400") }),
    );
    server.enqueue_json(
        Method::GET,
        "/v2/account/articles/40/files/400",
        StatusCode::OK,
        private_file_json(&server, 40, 400),
    );
    server.enqueue_json(
        Method::GET,
        "/upload/token-400",
        StatusCode::OK,
        json!({
            "token": "token-400",
            "name": "artifact.bin",
            "size": 6,
            "status": "PENDING",
            "parts": [
                { "partNo": 1, "startOffset": 0, "endOffset": 5, "status": "PENDING", "locked": false }
            ]
        }),
    );
    server.enqueue_text(
        Method::PUT,
        "/upload/token-400/1",
        StatusCode::INTERNAL_SERVER_ERROR,
        "upload failed",
    );
    server.enqueue_text(
        Method::DELETE,
        "/v2/account/articles/40/files/400",
        StatusCode::NO_CONTENT,
        "",
    );
    server.enqueue_text(
        Method::DELETE,
        "/v2/account/articles/40",
        StatusCode::NO_CONTENT,
        "",
    );

    let error = client
        .create_and_publish_article(
            &article_metadata(),
            vec![UploadSpec::from_path(&path).unwrap()],
        )
        .await
        .expect_err("upload failure expected");
    assert!(matches!(error, FigshareError::Http { .. }));

    let requests = server.requests();
    assert!(requests.iter().any(|request| {
        request.method == Method::DELETE && request.path == "/v2/account/articles/40"
    }));
}
