defmodule Aurora.Ctx.Test.Cases.CoreTest do
  use Aurora.Ctx.Test.RepoCase

  alias Aurora.Ctx.Core
  alias Aurora.Ctx.Repo
  alias Aurora.Ctx.Test.Support.Inventory.Product

  test "Test create function" do
    delete_all_products()

    {:ok, product} =
      Core.create(
        Repo,
        Product,
        %{"reference" => "item_1", "name" => "This is item 1", "cost" => 11.12}
      )

    assert product.reference == "item_1"
  end

  test "Test get functions" do
    delete_all_products()
    %{id_1: item_1, id_2: item_2, id_3: item_3} = create_sample_products(3)

    assert Core.get(Repo, Product, item_1.id).reference == "item_1"
    assert Core.get!(Repo, Product, item_2.id).reference == "item_2"
    assert Core.get!(Repo, Product, item_3.id).reference != "item_2"
    assert Core.get(Repo, Product, 1005) == nil
    assert_raise(Ecto.NoResultsError, fn -> Core.get!(Repo, Product, 1005) end)
  end

  test "Test delete functions" do
    delete_all_products()
    items = create_sample_products(3)

    {:ok, deleted_item_2} = Core.delete(Repo, items.id_2)
    assert deleted_item_2.reference == "item_2"

    assert_raise(Ecto.StaleEntryError, fn -> Core.delete(Repo, items.id_2) end)

    {:ok, deleted_item_3} = Core.delete(Repo, items.id_3)
    assert deleted_item_3.reference == "item_3"

    assert_raise(Ecto.StaleEntryError, fn -> Core.delete(Repo, items.id_3) end)
  end

  test "Test update function" do
    delete_all_products()
    items = create_sample_products(2)

    {:ok, update_item_1} =
      Core.update(Repo, items.id_1, %{description: "FIRST UPDATE test item_1"})

    assert update_item_1.description == "FIRST UPDATE test item_1"

    {:error, _} =
      Core.update(Repo, items.id_1, %{description: "SECOND UPDATE test item_1", cost: -1})

    {:ok, _} = Core.delete(Repo, items.id_2)

    assert_raise(Ecto.StaleEntryError, fn ->
      Core.update(Repo, items.id_2, %{description: "FIRST UPDATE test item_2"})
    end)

    items.id_1
    |> Core.change(%{description: "SECOND UPDATE test item_1"})
    |> then(&Core.update(Repo, &1))
    |> tap(&assert(elem(&1, 0) == :ok))
    |> elem(1)
    |> tap(&assert(&1.description == "SECOND UPDATE test item_1"))
  end

  test "Test new function" do
    Repo
    |> Core.new(Product)
    |> tap(&assert(&1.name == nil))
    |> tap(&assert(&1.product_transactions.__struct__ == Ecto.Association.NotLoaded))

    Repo
    |> Core.new(Product, %{name: "First name", reference: "item_chg_01", description: "The item"})
    |> tap(&assert(&1.name == "First name"))
    |> tap(&assert(&1.product_transactions.__struct__ == Ecto.Association.NotLoaded))

    Repo
    |> Core.new(Product, preload: :product_transactions)
    |> tap(&assert(&1.name == nil))
    |> tap(&assert(&1.product_transactions == []))

    Repo
    |> Core.new(Product, %{name: "First name", reference: "item_chg_01", description: "The item"},
      preload: :product_transactions
    )
    |> tap(&assert(&1.name == "First name"))
    |> tap(&assert(&1.product_transactions == []))
  end

  test "Test change function" do
    %Product{}
    |> Core.change()
    |> tap(&assert(Ecto.Changeset.get_field(&1, :reference) == nil))
    |> Core.change(%{reference: "item_chg_01"})
    |> tap(&assert(Ecto.Changeset.get_field(&1, :reference) == "item_chg_01"))
  end

  test "Test list function" do
    delete_all_products()
    items = create_sample_products(100)

    items_read = Core.list(Repo, Product)
    assert(Enum.count(items_read) == Enum.count(items))
  end

  test "Test list filter functionality - where and or_where" do
    delete_all_products()
    create_sample_products(100)

    assert(Repo |> Core.list(Product, where: {:reference, "item_001"}) |> Enum.count() == 1)

    assert(Repo |> Core.list(Product, where: {:reference, :eq, "item_090"}) |> Enum.count() == 1)

    assert(
      Repo |> Core.list(Product, where: [{:reference, :gt, "item_090"}]) |> Enum.count() == 10
    )

    assert(
      Repo |> Core.list(Product, where: [{:reference, :ge, "item_090"}]) |> Enum.count() == 11
    )

    assert(
      Repo |> Core.list(Product, where: [{:reference, :lt, "item_090"}]) |> Enum.count() == 89
    )

    assert(
      Repo |> Core.list(Product, where: [{:reference, :le, "item_090"}]) |> Enum.count() == 90
    )

    assert(
      Repo |> Core.list(Product, where: [{:reference, :like, "item\\_00_"}]) |> Enum.count() == 9
    )

    assert(
      Repo |> Core.list(Product, where: [{:reference, :ilike, "ITEM\\_00%"}]) |> Enum.count() == 9
    )

    assert(
      Repo
      |> Core.list(Product, where: {:reference, :between, "item_090", "item_095"})
      |> Enum.count() == 6
    )

    assert(
      Repo
      |> Core.list(Product,
        where: {:reference, :between, "item_090", "item_095"},
        or_where: {:reference, :eq, "item_001"}
      )
      |> Enum.count() == 7
    )

    assert(
      Repo
      |> Core.list(Product,
        where: dynamic([p], p.reference in ["item_001", "item_045", "item_063"])
      )
      |> Enum.count() == 3
    )
  end

  test "Test list bare pagination" do
    delete_all_products()
    create_sample_products(100)

    assert(
      Repo
      |> Core.list(Product, paginate: %{page: 1, per_page: 10})
      |> Enum.count() == 10
    )
  end

  test "Test bare count" do
    delete_all_products()
    create_sample_products(100)

    assert(Core.count(Repo, Product, paginate: %{page: 1, per_page: 10}) == 100)

    assert(
      Core.count(Repo, Product,
        paginate: %{page: 1, per_page: 10},
        where: {:reference, :between, "item_005", "item_015"}
      ) == 11
    )
  end

  test "Test List pagination" do
    delete_all_products()
    create_sample_products(100)

    Repo
    |> Core.list_paginated(Product, paginate: %{per_page: 5}, order_by: :reference)
    |> tap(&(assert(&1.entries_count) == 100))
    |> tap(&(assert(&1.pages_count) == 10))
    |> tap(&assert(Enum.count(&1.entries) == 5))
    |> tap(&assert(List.first(&1.entries).reference == "item_001"))
    |> tap(&assert(List.last(&1.entries).reference == "item_005"))
    |> Core.next_page()
    |> tap(&assert(Enum.count(&1.entries) == 5))
    |> tap(&assert(List.first(&1.entries).reference == "item_006"))
    |> tap(&assert(List.last(&1.entries).reference == "item_010"))
    |> Core.previous_page()
    |> tap(&assert(Enum.count(&1.entries) == 5))
    |> tap(&assert(List.first(&1.entries).reference == "item_001"))
    |> tap(&assert(List.last(&1.entries).reference == "item_005"))
    ## Should stay in the same page since there are only
    |> Core.to_page(30)
    |> tap(&assert(Enum.count(&1.entries) == 5))
    |> tap(&assert(List.first(&1.entries).reference == "item_001"))
    |> tap(&assert(List.last(&1.entries).reference == "item_005"))
    |> Core.to_page(11)
    |> tap(&assert(Enum.count(&1.entries) == 5))
    |> tap(&assert(List.first(&1.entries).reference == "item_051"))
    |> tap(&assert(List.last(&1.entries).reference == "item_055"))
    |> Core.to_page(10)
    |> tap(&assert(Enum.count(&1.entries) == 5))
    |> tap(&assert(List.first(&1.entries).reference == "item_046"))
    |> tap(&assert(List.last(&1.entries).reference == "item_050"))
  end

  test "Test List pagination DEFAULTS " do
    delete_all_products()
    create_sample_products(900)

    Repo
    |> Core.list_paginated(Product, order_by: :reference)
    |> tap(&(assert(&1.entries_count) == 900))
    |> tap(&(assert(&1.pages_count) == 40))
    |> tap(&assert(Enum.count(&1.entries) == 40))
    |> tap(&assert(List.first(&1.entries).reference == "item_001"))
    |> tap(&assert(List.last(&1.entries).reference == "item_040"))
    |> Core.next_page()
    |> tap(&assert(Enum.count(&1.entries) == 40))
    |> tap(&assert(List.first(&1.entries).reference == "item_041"))
    |> tap(&assert(List.last(&1.entries).reference == "item_080"))
    |> Core.previous_page()
    |> tap(&assert(Enum.count(&1.entries) == 40))
    |> tap(&assert(List.first(&1.entries).reference == "item_001"))
    |> tap(&assert(List.last(&1.entries).reference == "item_040"))
    ## Should stay in the same page since there are only
    |> Core.to_page(30)
    |> tap(&assert(Enum.count(&1.entries) == 40))
    |> tap(&assert(List.first(&1.entries).reference == "item_001"))
    |> tap(&assert(List.last(&1.entries).reference == "item_040"))
    |> Core.to_page(11)
    |> tap(&assert(Enum.count(&1.entries) == 40))
    |> tap(&assert(List.first(&1.entries).reference == "item_401"))
    |> tap(&assert(List.last(&1.entries).reference == "item_440"))
    |> Core.to_page(10)
    |> tap(&assert(Enum.count(&1.entries) == 40))
    |> tap(&assert(List.first(&1.entries).reference == "item_361"))
    |> tap(&assert(List.last(&1.entries).reference == "item_400"))
  end

  test "Test list pagination refresh" do
    delete_all_products()
    create_sample_products(100)

    paginate =
      Repo
      |> Core.list_paginated(Product, paginate: %{per_page: 5}, order_by: :reference)
      |> tap(&assert(Enum.count(&1.entries) == 5))
      |> tap(&assert(List.first(&1.entries).name == "Item 001"))
      |> tap(&assert(List.last(&1.entries).name == "Item 005"))

    item_1 = List.first(paginate.entries)
    item_5 = List.last(paginate.entries)

    Core.update(Repo, item_1, %{name: "New Item Name 001"})
    Core.update(Repo, item_5, %{name: "New Item Name 005"})

    paginate
    |> Core.previous_page()
    |> tap(&assert(Enum.count(&1.entries) == 5))
    |> tap(&assert(List.first(&1.entries).name == "New Item Name 001"))
    |> tap(&assert(List.last(&1.entries).name == "New Item Name 005"))
  end

  test "Test list sorting" do
    delete_all_products()
    create_sample_products(100)

    Repo
    |> Core.list(Product, order_by: [asc: :reference])
    |> tap(&assert(List.first(&1).reference == "item_001"))

    Repo
    |> Core.list(Product, order_by: [desc: :reference])
    |> tap(&assert(List.first(&1).reference == "item_100"))
  end

  test "Test get_by function" do
    delete_all_products()
    create_sample_products(100)

    Repo
    |> Core.get_by(Product, reference: "item_100")
    |> tap(&assert(&1.reference == "item_100"))

    Repo
    |> Core.get_by(Product, [], where: [reference: "item_098"])
    |> tap(&assert(&1.reference == "item_098"))
  end

  test "Test select options" do
    delete_all_products()
    create_sample_products(1)

    Repo
    |> Core.list(Product, select: :reference)
    |> List.first()
    |> tap(&assert(&1.reference == "item_1"))
    |> tap(&assert(&1.id == nil))

    Repo
    |> Core.list(Product, select: [:id, :reference])
    |> List.first()
    |> tap(&assert(&1.reference == "item_1"))
    |> tap(&refute(&1.id == nil))

    Repo
    |> Core.list(Product, select: dynamic([p], %{reference: p.reference, id: nil}))
    |> List.first()
    |> tap(&assert(&1.reference == "item_1"))
    |> tap(&assert(&1.id == nil))

    delete_all_products()
    create_sample_products(10)

    assert Repo
           |> Core.list(Product, select: dynamic([p], [p.reference]))
           |> List.flatten() == [
             "item_01",
             "item_02",
             "item_03",
             "item_04",
             "item_05",
             "item_06",
             "item_07",
             "item_08",
             "item_09",
             "item_10"
           ]
  end
end
