defmodule PurgeTest do
  use CouchTestCase

  @moduletag :purge

  @tag :with_db
  test "purge documents", context do
    db_name = context[:db_name]

    design_doc = %{
      _id: "_design/test",
      language: "javascript",
      views: %{
        all_docs_twice: %{
          map: "function(doc) { emit(doc.integer, null); emit(doc.integer, null) }"
        },
        single_doc: %{
          map: "function(doc) { if (doc._id == \"1\") { emit(1, null) }}"
        }
      }
    }

    {:ok, _} = create_doc(db_name, design_doc)

    num_docs = 10
    bulk_save(db_name, make_docs(1..(num_docs + 1)))

    test_all_docs_twice(db_name, num_docs, 1)

    info = info(db_name)

    doc1 = open_doc(db_name, 1)
    doc2 = open_doc(db_name, 2)

    resp =
      Couch.post("/#{db_name}/_purge",
        body: %{"1": [doc1["_rev"]], "2": [doc2["_rev"]]}
      )

    assert resp.status_code == 201
    result = resp.body

    assert Enum.at(result["purged"]["1"], 0) == doc1["_rev"]
    assert Enum.at(result["purged"]["2"], 0) == doc2["_rev"]

    open_doc(db_name, 1, 404)
    open_doc(db_name, 2, 404)

    purged_info = info(db_name)

    assert purged_info["purge_seq"] != info["purge_seq"]

    test_all_docs_twice(db_name, num_docs, 0, 2)

    # purge sequences are preserved after compaction (COUCHDB-1021)
    compact(db_name)

    compacted_info = info(db_name)
    assert compacted_info["purge_seq"] == purged_info["purge_seq"]

    # purge documents twice in a row without loading views
    # (causes full view rebuilds)

    doc3 = open_doc(db_name, 3)
    doc4 = open_doc(db_name, 4)

    resp =
      Couch.post("/#{db_name}/_purge",
        body: %{"3": [doc3["_rev"]]}
      )

    assert resp.status_code == 201

    resp =
      Couch.post("/#{db_name}/_purge",
        body: %{"4": [doc4["_rev"]]}
      )

    assert resp.status_code == 201

    test_all_docs_twice(db_name, num_docs, 0, 4)
  end

  @tag :with_db
  test "COUCHDB-1065", context do
    db_name_a = context[:db_name]
    db_name_b = random_db_name()
    {:ok, _} = create_db(db_name_b)

    {:ok, doc_a_resp} = create_doc(db_name_a, %{_id: "test", a: 1})
    {:ok, doc_b_resp} = create_doc(db_name_b, %{_id: "test", a: 2})
    replicate(db_name_a, db_name_b)

    open_rev(db_name_b, "test", doc_a_resp.body["rev"], 200)
    open_rev(db_name_b, "test", doc_b_resp.body["rev"], 200)

    resp =
      Couch.post("/#{db_name_b}/_purge",
        body: %{test: [doc_a_resp.body["rev"]]}
      )

    assert resp.status_code == 201

    open_rev(db_name_b, "test", doc_a_resp.body["rev"], 404)

    resp =
      Couch.post("/#{db_name_b}/_purge",
        body: %{test: [doc_b_resp.body["rev"]]}
      )

    assert resp.status_code == 201

    open_rev(db_name_b, "test", doc_b_resp.body["rev"], 404)

    resp =
      Couch.post("/#{db_name_b}/_purge",
        body: %{test: [doc_a_resp.body["rev"], doc_b_resp.body["rev"]]}
      )

    assert resp.status_code == 201

    delete_db(db_name_b)
  end

  defp open_doc(db_name, id, expect \\ 200) do
    resp = Couch.get("/#{db_name}/#{id}")
    assert resp.status_code == expect
    resp.body
  end

  defp open_rev(db_name, id, rev, expect) do
    resp = Couch.get("/#{db_name}/#{id}?rev=#{rev}")
    assert resp.status_code == expect
    resp.body
  end

  defp test_all_docs_twice(db_name, num_docs, sigle_doc_expect, offset \\ 0) do
    resp = Couch.get("/#{db_name}/_design/test/_view/all_docs_twice")
    assert resp.status_code == 200
    rows = resp.body["rows"]

    for x <- 0..(num_docs - offset) do
      assert Map.get(Enum.at(rows, 2 * x), "key") == x + offset + 1
      assert Map.get(Enum.at(rows, 2 * x + 1), "key") == x + offset + 1
    end

    resp = Couch.get("/#{db_name}/_design/test/_view/single_doc")
    assert resp.body["total_rows"] == sigle_doc_expect
  end
end
