mod errors;

use meili_snap::{json_string, snapshot};
use serde_json::json;

use crate::common::{GetAllDocumentsOptions, Server};

#[actix_rt::test]
async fn swap_indexes() {
    let server = Server::new().await;
    let a = server.index("a");
    let (_, code) = a.add_documents(json!({ "id": 1, "index": "a"}), None).await;
    snapshot!(code, @"202 Accepted");
    let b = server.index("b");
    let (res, code) = b.add_documents(json!({ "id": 1, "index": "b"}), None).await;
    snapshot!(code, @"202 Accepted");
    snapshot!(res["taskUid"], @"1");
    server.wait_task(1).await;

    let (tasks, code) = server.tasks().await;
    snapshot!(code, @"200 OK");
    snapshot!(json_string!(tasks, { ".results[].duration" => "[duration]", ".results[].enqueuedAt" => "[date]", ".results[].startedAt" => "[date]", ".results[].finishedAt" => "[date]" }), @r###"
    {
      "results": [
        {
          "uid": 1,
          "indexUid": "b",
          "status": "succeeded",
          "type": "documentAdditionOrUpdate",
          "canceledBy": null,
          "details": {
            "receivedDocuments": 1,
            "indexedDocuments": 1
          },
          "error": null,
          "duration": "[duration]",
          "enqueuedAt": "[date]",
          "startedAt": "[date]",
          "finishedAt": "[date]"
        },
        {
          "uid": 0,
          "indexUid": "a",
          "status": "succeeded",
          "type": "documentAdditionOrUpdate",
          "canceledBy": null,
          "details": {
            "receivedDocuments": 1,
            "indexedDocuments": 1
          },
          "error": null,
          "duration": "[duration]",
          "enqueuedAt": "[date]",
          "startedAt": "[date]",
          "finishedAt": "[date]"
        }
      ],
      "limit": 20,
      "from": 1,
      "next": null
    }
    "###);

    let (res, code) = server.index_swap(json!([{ "indexes": ["a", "b"] }])).await;
    snapshot!(code, @"202 Accepted");
    snapshot!(res["taskUid"], @"2");
    server.wait_task(2).await;

    let (tasks, code) = server.tasks().await;
    snapshot!(code, @"200 OK");

    // Notice how the task 0 which was initially representing the creation of the index `A` now represents the creation of the index `B`.
    snapshot!(json_string!(tasks, { ".results[].duration" => "[duration]", ".results[].enqueuedAt" => "[date]", ".results[].startedAt" => "[date]", ".results[].finishedAt" => "[date]" }), @r###"
    {
      "results": [
        {
          "uid": 2,
          "indexUid": null,
          "status": "succeeded",
          "type": "indexSwap",
          "canceledBy": null,
          "details": {
            "swaps": [
              {
                "indexes": [
                  "a",
                  "b"
                ]
              }
            ]
          },
          "error": null,
          "duration": "[duration]",
          "enqueuedAt": "[date]",
          "startedAt": "[date]",
          "finishedAt": "[date]"
        },
        {
          "uid": 1,
          "indexUid": "a",
          "status": "succeeded",
          "type": "documentAdditionOrUpdate",
          "canceledBy": null,
          "details": {
            "receivedDocuments": 1,
            "indexedDocuments": 1
          },
          "error": null,
          "duration": "[duration]",
          "enqueuedAt": "[date]",
          "startedAt": "[date]",
          "finishedAt": "[date]"
        },
        {
          "uid": 0,
          "indexUid": "b",
          "status": "succeeded",
          "type": "documentAdditionOrUpdate",
          "canceledBy": null,
          "details": {
            "receivedDocuments": 1,
            "indexedDocuments": 1
          },
          "error": null,
          "duration": "[duration]",
          "enqueuedAt": "[date]",
          "startedAt": "[date]",
          "finishedAt": "[date]"
        }
      ],
      "limit": 20,
      "from": 2,
      "next": null
    }
    "###);

    // BUT, the data in `a` should now points to the data that was in `b`.
    // And the opposite is true as well
    let (res, _) = a.get_all_documents(GetAllDocumentsOptions::default()).await;
    snapshot!(res["results"], @r###"[{"id":1,"index":"b"}]"###);
    let (res, _) = b.get_all_documents(GetAllDocumentsOptions::default()).await;
    snapshot!(res["results"], @r###"[{"id":1,"index":"a"}]"###);

    // ================
    // And now we're going to attempt the famous and dangerous DOUBLE index swap 🤞

    let c = server.index("c");
    let (res, code) = c.add_documents(json!({ "id": 1, "index": "c"}), None).await;
    snapshot!(code, @"202 Accepted");
    snapshot!(res["taskUid"], @"3");
    let d = server.index("d");
    let (res, code) = d.add_documents(json!({ "id": 1, "index": "d"}), None).await;
    snapshot!(code, @"202 Accepted");
    snapshot!(res["taskUid"], @"4");
    server.wait_task(4).await;

    // ensure the index creation worked properly
    let (tasks, code) = server.tasks_filter("limit=2").await;
    snapshot!(code, @"200 OK");
    snapshot!(json_string!(tasks, { ".results[].duration" => "[duration]", ".results[].enqueuedAt" => "[date]", ".results[].startedAt" => "[date]", ".results[].finishedAt" => "[date]" }), @r###"
    {
      "results": [
        {
          "uid": 4,
          "indexUid": "d",
          "status": "succeeded",
          "type": "documentAdditionOrUpdate",
          "canceledBy": null,
          "details": {
            "receivedDocuments": 1,
            "indexedDocuments": 1
          },
          "error": null,
          "duration": "[duration]",
          "enqueuedAt": "[date]",
          "startedAt": "[date]",
          "finishedAt": "[date]"
        },
        {
          "uid": 3,
          "indexUid": "c",
          "status": "succeeded",
          "type": "documentAdditionOrUpdate",
          "canceledBy": null,
          "details": {
            "receivedDocuments": 1,
            "indexedDocuments": 1
          },
          "error": null,
          "duration": "[duration]",
          "enqueuedAt": "[date]",
          "startedAt": "[date]",
          "finishedAt": "[date]"
        }
      ],
      "limit": 2,
      "from": 4,
      "next": 2
    }
    "###);

    // It's happening 😲

    let (res, code) =
        server.index_swap(json!([{ "indexes": ["a", "b"] }, { "indexes": ["c", "d"] } ])).await;
    snapshot!(res["taskUid"], @"5");
    snapshot!(code, @"202 Accepted");
    server.wait_task(5).await;

    // ensure the index creation worked properly
    let (tasks, code) = server.tasks().await;
    snapshot!(code, @"200 OK");

    // What should we check for each tasks in this test:
    // Task number;
    // 0. should have the indexUid `a` again
    // 1. should have the indexUid `b` again
    // 2. stays unchanged
    // 3. now have the indexUid `d` instead of `c`
    // 4. now have the indexUid `c` instead of `d`
    snapshot!(json_string!(tasks, { ".results[].duration" => "[duration]", ".results[].enqueuedAt" => "[date]", ".results[].startedAt" => "[date]", ".results[].finishedAt" => "[date]" }), @r###"
    {
      "results": [
        {
          "uid": 5,
          "indexUid": null,
          "status": "succeeded",
          "type": "indexSwap",
          "canceledBy": null,
          "details": {
            "swaps": [
              {
                "indexes": [
                  "a",
                  "b"
                ]
              },
              {
                "indexes": [
                  "c",
                  "d"
                ]
              }
            ]
          },
          "error": null,
          "duration": "[duration]",
          "enqueuedAt": "[date]",
          "startedAt": "[date]",
          "finishedAt": "[date]"
        },
        {
          "uid": 4,
          "indexUid": "c",
          "status": "succeeded",
          "type": "documentAdditionOrUpdate",
          "canceledBy": null,
          "details": {
            "receivedDocuments": 1,
            "indexedDocuments": 1
          },
          "error": null,
          "duration": "[duration]",
          "enqueuedAt": "[date]",
          "startedAt": "[date]",
          "finishedAt": "[date]"
        },
        {
          "uid": 3,
          "indexUid": "d",
          "status": "succeeded",
          "type": "documentAdditionOrUpdate",
          "canceledBy": null,
          "details": {
            "receivedDocuments": 1,
            "indexedDocuments": 1
          },
          "error": null,
          "duration": "[duration]",
          "enqueuedAt": "[date]",
          "startedAt": "[date]",
          "finishedAt": "[date]"
        },
        {
          "uid": 2,
          "indexUid": null,
          "status": "succeeded",
          "type": "indexSwap",
          "canceledBy": null,
          "details": {
            "swaps": [
              {
                "indexes": [
                  "b",
                  "a"
                ]
              }
            ]
          },
          "error": null,
          "duration": "[duration]",
          "enqueuedAt": "[date]",
          "startedAt": "[date]",
          "finishedAt": "[date]"
        },
        {
          "uid": 1,
          "indexUid": "b",
          "status": "succeeded",
          "type": "documentAdditionOrUpdate",
          "canceledBy": null,
          "details": {
            "receivedDocuments": 1,
            "indexedDocuments": 1
          },
          "error": null,
          "duration": "[duration]",
          "enqueuedAt": "[date]",
          "startedAt": "[date]",
          "finishedAt": "[date]"
        },
        {
          "uid": 0,
          "indexUid": "a",
          "status": "succeeded",
          "type": "documentAdditionOrUpdate",
          "canceledBy": null,
          "details": {
            "receivedDocuments": 1,
            "indexedDocuments": 1
          },
          "error": null,
          "duration": "[duration]",
          "enqueuedAt": "[date]",
          "startedAt": "[date]",
          "finishedAt": "[date]"
        }
      ],
      "limit": 20,
      "from": 5,
      "next": null
    }
    "###);

    // - The data in `a` should point to `a`.
    // - The data in `b` should point to `b`.
    // - The data in `c` should point to `d`.
    // - The data in `d` should point to `c`.
    let (res, _) = a.get_all_documents(GetAllDocumentsOptions::default()).await;
    snapshot!(res["results"], @r###"[{"id":1,"index":"a"}]"###);
    let (res, _) = b.get_all_documents(GetAllDocumentsOptions::default()).await;
    snapshot!(res["results"], @r###"[{"id":1,"index":"b"}]"###);
    let (res, _) = c.get_all_documents(GetAllDocumentsOptions::default()).await;
    snapshot!(res["results"], @r###"[{"id":1,"index":"d"}]"###);
    let (res, _) = d.get_all_documents(GetAllDocumentsOptions::default()).await;
    snapshot!(res["results"], @r###"[{"id":1,"index":"c"}]"###);
}