defmodule Berrypod.Clients.PrintfulHttpTest do
  @moduledoc """
  Tests the Printful HTTP client against Req.Test stubs.
  Exercises URL construction, auth headers, response unwrapping, and error handling.
  """

  use ExUnit.Case, async: true

  alias Berrypod.Clients.Printful

  setup do
    Process.put(:printful_api_key, "test_token_abc")
    Req.Test.stub(Printful, &route/1)
    :ok
  end

  # =============================================================================
  # Response unwrapping
  # =============================================================================

  describe "v1 response unwrapping" do
    test "unwraps result key from v1 responses" do
      assert {:ok, [%{"id" => 456}]} = Printful.list_sync_products()
    end

    test "get_sync_product returns nested data" do
      assert {:ok, data} = Printful.get_sync_product(456)
      assert data["sync_product"]["name"] == "Test T-Shirt"
    end
  end

  describe "v2 response unwrapping" do
    test "unwraps data key from v2 responses" do
      assert {:ok, stores} = Printful.get_stores()
      assert [%{"id" => 123, "name" => "Test Store"}] = stores
    end

    test "get_order returns v2 data" do
      assert {:ok, order} = Printful.get_order(12345)
      assert order["status"] == "draft"
    end
  end

  # =============================================================================
  # Error handling
  # =============================================================================

  describe "error handling" do
    test "returns error tuple on 4xx" do
      Req.Test.stub(Printful, fn conn ->
        Req.Test.json(conn |> Plug.Conn.put_status(404), %{"error" => "Not found"})
      end)

      assert {:error, {404, %{"error" => "Not found"}}} = Printful.get("/v2/orders/999")
    end

    test "returns error tuple on 5xx" do
      Req.Test.stub(Printful, fn conn ->
        Req.Test.json(conn |> Plug.Conn.put_status(500), %{"error" => "Internal error"})
      end)

      assert {:error, {500, _}} = Printful.get("/v2/stores")
    end

    test "post returns error on 422" do
      Req.Test.stub(Printful, fn conn ->
        Req.Test.json(
          conn |> Plug.Conn.put_status(422),
          %{"error" => "Invalid address"}
        )
      end)

      assert {:error, {422, %{"error" => "Invalid address"}}} =
               Printful.create_order(%{test: true})
    end
  end

  # =============================================================================
  # Auth headers
  # =============================================================================

  describe "auth headers" do
    test "includes Bearer token" do
      Req.Test.stub(Printful, fn conn ->
        [auth] = Plug.Conn.get_req_header(conn, "authorization")
        assert auth == "Bearer test_token_abc"
        Req.Test.json(conn, %{"data" => []})
      end)

      assert {:ok, _} = Printful.get_stores()
    end

    test "includes X-PF-Store-Id when set" do
      Process.put(:printful_store_id, 99999)

      Req.Test.stub(Printful, fn conn ->
        [store_id] = Plug.Conn.get_req_header(conn, "x-pf-store-id")
        assert store_id == "99999"
        Req.Test.json(conn, %{"code" => 200, "result" => []})
      end)

      assert {:ok, _} = Printful.list_sync_products()
    after
      Process.delete(:printful_store_id)
    end

    test "omits X-PF-Store-Id when not set" do
      Process.delete(:printful_store_id)

      Req.Test.stub(Printful, fn conn ->
        assert Plug.Conn.get_req_header(conn, "x-pf-store-id") == []
        Req.Test.json(conn, %{"data" => []})
      end)

      assert {:ok, _} = Printful.get_stores()
    end
  end

  # =============================================================================
  # Specific endpoints
  # =============================================================================

  describe "get_stores/0" do
    test "calls GET /v2/stores" do
      Req.Test.stub(Printful, fn conn ->
        assert conn.method == "GET"
        assert conn.request_path == "/v2/stores"
        Req.Test.json(conn, %{"data" => [%{"id" => 1}]})
      end)

      assert {:ok, [%{"id" => 1}]} = Printful.get_stores()
    end
  end

  describe "get_store_id/0" do
    test "returns first store id" do
      assert {:ok, 123} = Printful.get_store_id()
    end

    test "returns error when no stores" do
      Req.Test.stub(Printful, fn conn ->
        Req.Test.json(conn, %{"data" => []})
      end)

      assert {:error, :no_stores} = Printful.get_store_id()
    end
  end

  describe "list_sync_products/1" do
    test "passes offset and limit as query params" do
      Req.Test.stub(Printful, fn conn ->
        assert conn.query_string == "offset=40&limit=20"
        Req.Test.json(conn, %{"code" => 200, "result" => []})
      end)

      assert {:ok, []} = Printful.list_sync_products(offset: 40)
    end

    test "defaults to offset 0 and limit 20" do
      Req.Test.stub(Printful, fn conn ->
        assert conn.query_string == "offset=0&limit=20"
        Req.Test.json(conn, %{"code" => 200, "result" => []})
      end)

      assert {:ok, []} = Printful.list_sync_products()
    end
  end

  describe "calculate_shipping/2" do
    test "sends recipient and items in POST body" do
      Req.Test.stub(Printful, fn conn ->
        assert conn.method == "POST"
        assert conn.request_path == "/v2/shipping-rates"

        {:ok, body, _conn} = Plug.Conn.read_body(conn)
        decoded = Jason.decode!(body)
        assert decoded["recipient"]["country_code"] == "GB"
        assert length(decoded["order_items"]) == 1

        Req.Test.json(conn, %{
          "data" => [%{"shipping" => "STANDARD", "rate" => "4.99", "currency" => "USD"}]
        })
      end)

      recipient = %{country_code: "GB"}
      items = [%{source: "catalog", catalog_variant_id: 474, quantity: 1}]
      assert {:ok, [rate]} = Printful.calculate_shipping(recipient, items)
      assert rate["rate"] == "4.99"
    end
  end

  describe "create_order/1 and confirm_order/1" do
    test "create_order sends POST to /v2/orders" do
      Req.Test.stub(Printful, fn conn ->
        assert conn.method == "POST"
        assert conn.request_path == "/v2/orders"
        Req.Test.json(conn, %{"data" => %{"id" => 12345, "status" => "draft"}})
      end)

      assert {:ok, %{"id" => 12345}} = Printful.create_order(%{external_id: "SS-001"})
    end

    test "confirm_order sends POST to /v2/orders/:id/confirmation" do
      Req.Test.stub(Printful, fn conn ->
        assert conn.method == "POST"
        assert conn.request_path == "/v2/orders/12345/confirmation"
        Req.Test.json(conn, %{"data" => %{"id" => 12345, "status" => "pending"}})
      end)

      assert {:ok, %{"status" => "pending"}} = Printful.confirm_order(12345)
    end
  end

  describe "get_order_shipments/1" do
    test "calls GET /v2/orders/:id/shipments" do
      Req.Test.stub(Printful, fn conn ->
        assert conn.request_path == "/v2/orders/12345/shipments"

        Req.Test.json(conn, %{
          "data" => [%{"tracking_number" => "1Z999", "tracking_url" => "https://ups.com/1Z999"}]
        })
      end)

      assert {:ok, [shipment]} = Printful.get_order_shipments(12345)
      assert shipment["tracking_number"] == "1Z999"
    end
  end

  describe "setup_webhooks/2" do
    test "sends POST to /v2/webhooks with url and events" do
      Req.Test.stub(Printful, fn conn ->
        assert conn.method == "POST"
        assert conn.request_path == "/v2/webhooks"

        {:ok, body, _conn} = Plug.Conn.read_body(conn)
        decoded = Jason.decode!(body)
        assert decoded["url"] =~ "https://example.com"
        assert is_list(decoded["events"])

        Req.Test.json(conn, %{"data" => %{"url" => decoded["url"]}})
      end)

      assert {:ok, _} =
               Printful.setup_webhooks("https://example.com/webhooks", ["package_shipped"])
    end
  end

  describe "mockup generator" do
    test "create_mockup_generator_task sends POST to /mockup-generator/create-task/:id" do
      Req.Test.stub(Printful, fn conn ->
        assert conn.method == "POST"
        assert conn.request_path == "/mockup-generator/create-task/71"

        Req.Test.json(conn, %{
          "code" => 200,
          "result" => %{"task_key" => "gt-abc123", "status" => "pending"}
        })
      end)

      assert {:ok, %{"task_key" => "gt-abc123"}} =
               Printful.create_mockup_generator_task(71, %{variant_ids: [4011]})
    end

    test "get_mockup_generator_task polls by task key" do
      Req.Test.stub(Printful, fn conn ->
        assert conn.query_string =~ "task_key=gt-abc123"

        Req.Test.json(conn, %{
          "code" => 200,
          "result" => %{"status" => "completed", "mockups" => []}
        })
      end)

      assert {:ok, %{"status" => "completed"}} =
               Printful.get_mockup_generator_task("gt-abc123")
    end
  end

  describe "delete/1" do
    test "delete_webhooks calls DELETE /v2/webhooks" do
      Req.Test.stub(Printful, fn conn ->
        assert conn.method == "DELETE"
        assert conn.request_path == "/v2/webhooks"
        Plug.Conn.send_resp(conn, 204, "")
      end)

      assert {:ok, nil} = Printful.delete_webhooks()
    end
  end

  # =============================================================================
  # Default stub router — handles all standard routes for basic tests
  # =============================================================================

  defp route(%Plug.Conn{method: "GET", request_path: "/v2/stores"} = conn) do
    Req.Test.json(conn, %{"data" => [%{"id" => 123, "name" => "Test Store"}]})
  end

  defp route(%Plug.Conn{method: "GET", request_path: "/v2/orders/" <> rest} = conn) do
    cond do
      String.contains?(rest, "/shipments") ->
        Req.Test.json(conn, %{"data" => []})

      true ->
        Req.Test.json(conn, %{
          "data" => %{"id" => 12345, "status" => "draft", "external_id" => "SS-001"}
        })
    end
  end

  defp route(%Plug.Conn{method: "GET", request_path: "/store/products/" <> _id} = conn) do
    Req.Test.json(conn, %{
      "code" => 200,
      "result" => %{
        "sync_product" => %{"id" => 456, "name" => "Test T-Shirt", "thumbnail_url" => nil},
        "sync_variants" => []
      }
    })
  end

  defp route(%Plug.Conn{method: "GET", request_path: "/store/products"} = conn) do
    Req.Test.json(conn, %{"code" => 200, "result" => [%{"id" => 456}]})
  end

  defp route(%Plug.Conn{method: "GET"} = conn) do
    Req.Test.json(conn, %{"data" => %{}})
  end

  defp route(%Plug.Conn{method: "POST"} = conn) do
    Req.Test.json(conn, %{"data" => %{}})
  end

  defp route(%Plug.Conn{method: "DELETE"} = conn) do
    Plug.Conn.send_resp(conn, 204, "")
  end
end
