defmodule Berrypod.OrdersTest do
  use Berrypod.DataCase, async: false

  import Ecto.Query
  import Mox
  import Berrypod.OrdersFixtures

  alias Berrypod.Orders
  alias Berrypod.Providers.MockProvider

  setup :verify_on_exit!

  describe "list_orders/1" do
    test "returns all orders" do
      order1 = order_fixture()
      order2 = order_fixture()

      orders = Orders.list_orders()
      order_ids = Enum.map(orders, & &1.id)

      assert order1.id in order_ids
      assert order2.id in order_ids
      assert length(orders) == 2
    end

    test "filters by payment status" do
      _pending = order_fixture()
      paid = order_fixture(payment_status: "paid")
      _failed = order_fixture(payment_status: "failed")

      orders = Orders.list_orders(status: "paid")
      assert length(orders) == 1
      assert hd(orders).id == paid.id
    end

    test "returns all when status is 'all'" do
      order_fixture()
      order_fixture(payment_status: "paid")

      orders = Orders.list_orders(status: "all")
      assert length(orders) == 2
    end

    test "preloads items" do
      order_fixture()

      [order] = Orders.list_orders()
      assert Ecto.assoc_loaded?(order.items)
      assert length(order.items) == 1
    end
  end

  describe "count_orders_by_status/0" do
    test "returns empty map when no orders" do
      assert Orders.count_orders_by_status() == %{}
    end

    test "counts orders by status" do
      order_fixture()
      order_fixture()
      order_fixture(payment_status: "paid")
      order_fixture(payment_status: "failed")

      counts = Orders.count_orders_by_status()
      assert counts["pending"] == 2
      assert counts["paid"] == 1
      assert counts["failed"] == 1
    end
  end

  describe "submit_to_provider/1" do
    setup do
      Application.put_env(:berrypod, :provider_modules, %{
        "printify" => MockProvider
      })

      on_exit(fn -> Application.delete_env(:berrypod, :provider_modules) end)
      :ok
    end

    test "submits order and sets fulfilment status" do
      {order, _variant, _product, _conn} = paid_order_with_products_fixture()

      expect(MockProvider, :submit_order, fn _conn, order_data ->
        assert order_data.order_number == order.order_number
        assert is_list(order_data.line_items)
        assert hd(order_data.line_items).quantity == 1
        {:ok, %{provider_order_id: "pfy_123"}}
      end)

      assert {:ok, updated} = Orders.submit_to_provider(order)
      assert updated.fulfilment_status == "submitted"
      assert updated.provider_order_id == "pfy_123"
      assert updated.submitted_at != nil
      assert updated.fulfilment_error == nil
    end

    test "is idempotent when already submitted" do
      {order, _variant, _product, _conn} = submitted_order_fixture()

      # No mock expectations — provider should not be called
      assert {:ok, ^order} = Orders.submit_to_provider(order)
    end

    test "sets failed status on provider error" do
      {order, _variant, _product, _conn} = paid_order_with_products_fixture()

      expect(MockProvider, :submit_order, fn _conn, _data ->
        {:error, {500, %{"message" => "Server error"}}}
      end)

      assert {:error, {500, _}} = Orders.submit_to_provider(order)

      updated = Orders.get_order(order.id)
      assert updated.fulfilment_status == "failed"
      assert updated.fulfilment_error =~ "Provider API error (500)"
    end
  end

  describe "refresh_fulfilment_status/1" do
    setup do
      Application.put_env(:berrypod, :provider_modules, %{
        "printify" => MockProvider
      })

      on_exit(fn -> Application.delete_env(:berrypod, :provider_modules) end)
      :ok
    end

    test "updates tracking info from provider" do
      {order, _variant, _product, _conn} = submitted_order_fixture()

      expect(MockProvider, :get_order_status, fn _conn, pid ->
        assert pid == order.provider_order_id

        {:ok,
         %{
           status: "shipped",
           provider_status: "shipped",
           tracking_number: "TRACK123",
           tracking_url: "https://track.example.com/TRACK123"
         }}
      end)

      assert {:ok, updated} = Orders.refresh_fulfilment_status(order)
      assert updated.fulfilment_status == "shipped"
      assert updated.tracking_number == "TRACK123"
      assert updated.tracking_url == "https://track.example.com/TRACK123"
      assert updated.shipped_at != nil
    end

    test "no-op when no provider_order_id" do
      order = order_fixture(payment_status: "paid")
      assert is_nil(order.provider_order_id)

      assert {:ok, ^order} = Orders.refresh_fulfilment_status(order)
    end
  end

  describe "update_fulfilment/2" do
    test "updates fulfilment fields" do
      order = order_fixture()

      assert {:ok, updated} =
               Orders.update_fulfilment(order, %{
                 fulfilment_status: "submitted",
                 provider_order_id: "test_123"
               })

      assert updated.fulfilment_status == "submitted"
      assert updated.provider_order_id == "test_123"
    end

    test "validates fulfilment status inclusion" do
      order = order_fixture()

      assert {:error, changeset} =
               Orders.update_fulfilment(order, %{fulfilment_status: "bogus"})

      assert errors_on(changeset).fulfilment_status != []
    end
  end

  describe "list_submitted_orders/0" do
    test "returns orders with submitted or processing status" do
      {submitted, _v, _p, _c} = submitted_order_fixture()

      {processing, _v2, _p2, _c2} = submitted_order_fixture()

      {:ok, processing} =
        Orders.update_fulfilment(processing, %{fulfilment_status: "processing"})

      _unfulfilled = order_fixture(payment_status: "paid")

      orders = Orders.list_submitted_orders()
      ids = Enum.map(orders, & &1.id)

      assert submitted.id in ids
      assert processing.id in ids
      assert length(orders) == 2
    end

    test "returns empty list when no submitted orders" do
      order_fixture()
      assert Orders.list_submitted_orders() == []
    end
  end

  describe "get_order_by_number/1" do
    test "finds order by order number" do
      order = order_fixture()
      found = Orders.get_order_by_number(order.order_number)
      assert found.id == order.id
    end

    test "returns nil for unknown order number" do
      assert is_nil(Orders.get_order_by_number("SS-000000-XXXX"))
    end
  end

  describe "list_orders_by_email/1" do
    test "returns paid orders for the given email" do
      order = order_fixture(%{customer_email: "buyer@example.com", payment_status: "paid"})

      results = Orders.list_orders_by_email("buyer@example.com")
      ids = Enum.map(results, & &1.id)

      assert order.id in ids
    end

    test "excludes pending and failed orders" do
      order_fixture(%{customer_email: "buyer@example.com"})
      order_fixture(%{customer_email: "buyer@example.com", payment_status: "failed"})

      assert Orders.list_orders_by_email("buyer@example.com") == []
    end

    test "excludes other customers' orders" do
      order_fixture(%{customer_email: "other@example.com", payment_status: "paid"})

      assert Orders.list_orders_by_email("buyer@example.com") == []
    end

    test "is case-insensitive" do
      order = order_fixture(%{customer_email: "Buyer@Example.COM", payment_status: "paid"})

      results = Orders.list_orders_by_email("buyer@example.com")
      ids = Enum.map(results, & &1.id)

      assert order.id in ids
    end

    test "preloads items" do
      order_fixture(%{customer_email: "buyer@example.com", payment_status: "paid"})

      [order] = Orders.list_orders_by_email("buyer@example.com")
      assert Ecto.assoc_loaded?(order.items)
    end

    test "returns newest first" do
      old = order_fixture(%{customer_email: "buyer@example.com", payment_status: "paid"})
      new = order_fixture(%{customer_email: "buyer@example.com", payment_status: "paid"})

      # Force different inserted_at by updating order records
      now = DateTime.utc_now() |> DateTime.truncate(:second)
      earlier = DateTime.add(now, -60, :second)

      Berrypod.Repo.update_all(
        from(o in Berrypod.Orders.Order, where: o.id == ^old.id),
        set: [inserted_at: earlier]
      )

      [first, second] = Orders.list_orders_by_email("buyer@example.com")
      assert first.id == new.id
      assert second.id == old.id
    end
  end
end
