defmodule Berrypod.Clients.Printful do
  @moduledoc """
  HTTP client for the Printful API.

  Uses v2 endpoints where available, v1 for sync products (store products).
  Handles authentication via Bearer tokens stored in the process dictionary.
  """

  @base_url "https://api.printful.com"

  # =============================================================================
  # Auth
  # =============================================================================

  @doc """
  Get the API token.

  Checks process dictionary first (for provider connections with stored credentials),
  then falls back to environment variable (for development/testing).
  """
  def api_token do
    Process.get(:printful_api_key) ||
      System.get_env("PRINTFUL_API_TOKEN") ||
      raise "PRINTFUL_API_TOKEN environment variable is not set"
  end

  @doc """
  Get the store ID from the process dictionary.
  """
  def store_id do
    Process.get(:printful_store_id)
  end

  # =============================================================================
  # Core HTTP
  # =============================================================================

  @doc """
  Make a GET request to the Printful API.
  """
  def get(path, _opts \\ []) do
    url = @base_url <> path

    case Req.get(url, [headers: auth_headers(), receive_timeout: 30_000] ++ base_options()) do
      {:ok, %Req.Response{status: status, body: body}} when status in 200..299 ->
        {:ok, unwrap_response(path, body)}

      {:ok, %Req.Response{status: status, body: body}} ->
        {:error, {status, body}}

      {:error, reason} ->
        {:error, reason}
    end
  end

  @doc """
  Make a POST request to the Printful API.
  """
  def post(path, body, _opts \\ []) do
    url = @base_url <> path

    case Req.post(
           url,
           [json: body, headers: auth_headers(), receive_timeout: 60_000] ++ base_options()
         ) do
      {:ok, %Req.Response{status: status, body: body}} when status in 200..299 ->
        {:ok, unwrap_response(path, body)}

      {:ok, %Req.Response{status: status, body: body}} ->
        {:error, {status, body}}

      {:error, reason} ->
        {:error, reason}
    end
  end

  @doc """
  Make a DELETE request to the Printful API.
  """
  def delete(path, _opts \\ []) do
    url = @base_url <> path

    case Req.delete(url, [headers: auth_headers(), receive_timeout: 30_000] ++ base_options()) do
      {:ok, %Req.Response{status: 204}} ->
        {:ok, nil}

      {:ok, %Req.Response{status: status, body: body}} when status in 200..299 ->
        {:ok, unwrap_response(path, body)}

      {:ok, %Req.Response{status: status, body: body}} ->
        {:error, {status, body}}

      {:error, reason} ->
        {:error, reason}
    end
  end

  # v1 responses wrap data in "result", v2 wraps in "data"
  defp unwrap_response("/v2/" <> _, %{"data" => data}), do: data
  defp unwrap_response(_path, %{"code" => _, "result" => result}), do: result
  defp unwrap_response(_path, body), do: body

  # =============================================================================
  # Stores (v2)
  # =============================================================================

  @doc """
  List all stores for the authenticated account.
  """
  def get_stores do
    get("/v2/stores")
  end

  @doc """
  Get the first store's ID.
  """
  def get_store_id do
    case get_stores() do
      {:ok, stores} when is_list(stores) and stores != [] ->
        {:ok, hd(stores)["id"]}

      {:ok, []} ->
        {:error, :no_stores}

      error ->
        error
    end
  end

  # =============================================================================
  # Catalogue (v2)
  # =============================================================================

  @doc """
  Get a catalogue product by ID.
  """
  def get_catalog_product(product_id) do
    get("/v2/catalog-products/#{product_id}")
  end

  @doc """
  Get variants for a catalogue product.
  """
  def get_catalog_variants(product_id) do
    get("/v2/catalog-products/#{product_id}/catalog-variants")
  end

  @doc """
  Get stock availability for a catalogue product.
  """
  def get_product_availability(product_id) do
    get("/v2/catalog-products/#{product_id}/availability")
  end

  # =============================================================================
  # Sync Products (v1 — no v2 equivalent)
  # =============================================================================

  @doc """
  List the seller's configured products (sync products).

  These are products the seller has set up in Printful's dashboard with designs.
  Supports pagination via `offset` and `limit` options.
  """
  def list_sync_products(opts \\ []) do
    offset = Keyword.get(opts, :offset, 0)
    limit = Keyword.get(opts, :limit, 20)
    get("/store/products?offset=#{offset}&limit=#{limit}")
  end

  @doc """
  Get a single sync product with all its variants and files.
  """
  def get_sync_product(product_id) do
    get("/store/products/#{product_id}")
  end

  @doc """
  Create a sync product with variants and design files.

  ## Example

      create_sync_product(%{
        sync_product: %{name: "My T-Shirt"},
        sync_variants: [%{
          variant_id: 4011,
          retail_price: "29.99",
          files: [%{url: "https://example.com/design.png", type: "default"}]
        }]
      })
  """
  def create_sync_product(product_data) do
    post("/store/products", product_data)
  end

  @doc """
  Delete a sync product and all its variants.
  """
  def delete_sync_product(product_id) do
    delete("/store/products/#{product_id}")
  end

  # =============================================================================
  # Shipping (v2)
  # =============================================================================

  @doc """
  Calculate shipping rates for a set of items to a recipient.

  ## Example

      calculate_shipping(
        %{country_code: "GB"},
        [%{source: "catalog", catalog_variant_id: 474, quantity: 1}]
      )
  """
  def calculate_shipping(recipient, items) do
    post("/v2/shipping-rates", %{recipient: recipient, order_items: items})
  end

  # =============================================================================
  # Orders (v2)
  # =============================================================================

  @doc """
  Create a new order (draft status).
  """
  def create_order(order_data) do
    post("/v2/orders", order_data)
  end

  @doc """
  Confirm an order for fulfilment. This triggers charges and production.
  """
  def confirm_order(order_id) do
    post("/v2/orders/#{order_id}/confirmation", %{})
  end

  @doc """
  Get an order by ID.
  """
  def get_order(order_id) do
    get("/v2/orders/#{order_id}")
  end

  @doc """
  Get shipments for an order.
  """
  def get_order_shipments(order_id) do
    get("/v2/orders/#{order_id}/shipments")
  end

  # =============================================================================
  # Mockups (v2)
  # =============================================================================

  @doc """
  Create a mockup generation task.
  """
  def create_mockup_task(body) do
    post("/v2/mockup-tasks", body)
  end

  @doc """
  Get mockup task results. Pass `task_id` to poll a specific task.
  """
  def get_mockup_tasks(params \\ %{}) do
    query = URI.encode_query(params)
    path = if query == "", do: "/v2/mockup-tasks", else: "/v2/mockup-tasks?#{query}"
    get(path)
  end

  # =============================================================================
  # Mockup generator (legacy, multi-angle)
  # =============================================================================

  @doc """
  Create a mockup generator task for a catalog product.

  Returns `{:ok, %{"task_key" => "gt-...", "status" => "pending"}}`.
  """
  def create_mockup_generator_task(catalog_product_id, body) do
    post("/mockup-generator/create-task/#{catalog_product_id}", body)
  end

  @doc """
  Poll a mockup generator task by task key.

  Returns `{:ok, %{"status" => "completed", "mockups" => [...]}}` when done.
  """
  def get_mockup_generator_task(task_key) do
    get("/mockup-generator/task?task_key=#{task_key}")
  end

  # =============================================================================
  # Files (v2)
  # =============================================================================

  @doc """
  Upload a file to the Printful file library.
  """
  def upload_file(url) do
    post("/v2/files", %{url: url})
  end

  # =============================================================================
  # Webhooks (v2)
  # =============================================================================

  @doc """
  Set up webhook configuration.
  """
  def setup_webhooks(url, events) do
    post("/v2/webhooks", %{url: url, events: events})
  end

  @doc """
  Get current webhook configuration.
  """
  def get_webhooks do
    get("/v2/webhooks")
  end

  @doc """
  Disable webhook support.
  """
  def delete_webhooks do
    delete("/v2/webhooks")
  end

  # =============================================================================
  # File Downloads
  # =============================================================================

  @doc """
  Download a file from a URL to a local path.
  """
  def download_file(url, output_path) do
    case Req.get(url, into: File.stream!(output_path), receive_timeout: 60_000) do
      {:ok, %Req.Response{status: status}} when status in 200..299 ->
        {:ok, output_path}

      {:ok, %Req.Response{status: status}} ->
        File.rm(output_path)
        {:error, {:http_error, status}}

      {:error, reason} ->
        File.rm(output_path)
        {:error, reason}
    end
  end

  # =============================================================================
  # Private
  # =============================================================================

  defp auth_headers do
    headers = [
      {"Authorization", "Bearer #{api_token()}"},
      {"Content-Type", "application/json"}
    ]

    case store_id() do
      nil -> headers
      id -> [{"X-PF-Store-Id", to_string(id)} | headers]
    end
  end

  defp base_options do
    Application.get_env(:berrypod, __MODULE__, [])[:req_options] || []
  end
end
