defmodule Plausible.Billing.PaddleApi do
  alias Plausible.HTTPClient

  @headers [
    {"content-type", "application/json"},
    {"accept", "application/json"}
  ]

  def update_subscription_preview(paddle_subscription_id, new_plan_id) do
    config = get_config()

    params = %{
      vendor_id: config[:vendor_id],
      vendor_auth_code: config[:vendor_auth_code],
      subscription_id: paddle_subscription_id,
      plan_id: new_plan_id,
      prorate: true,
      keep_modifiers: true,
      bill_immediately: true,
      quantity: 1
    }

    case HTTPClient.post(preview_update_url(), @headers, params) do
      {:ok, response} ->
        if response.body["success"] do
          {:ok, response.body["response"]}
        else
          {:error, response.body["error"]}
        end

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

  def update_subscription(paddle_subscription_id, params) do
    config = get_config()

    params =
      Map.merge(params, %{
        vendor_id: config[:vendor_id],
        vendor_auth_code: config[:vendor_auth_code],
        subscription_id: paddle_subscription_id,
        prorate: true,
        keep_modifiers: true,
        bill_immediately: true,
        quantity: 1
      })

    case HTTPClient.post(update_subscription_url(), @headers, params) do
      {:ok, response} ->
        if response.body["success"] do
          {:ok, response.body["response"]}
        else
          {:error, response.body["error"]}
        end

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

  def get_subscription(paddle_subscription_id) do
    config = get_config()

    params = %{
      vendor_id: config[:vendor_id],
      vendor_auth_code: config[:vendor_auth_code],
      subscription_id: paddle_subscription_id
    }

    case HTTPClient.post(get_subscription_url(), @headers, params) do
      {:ok, response} ->
        if response.body["success"] do
          [subscription] = response.body["response"]
          {:ok, subscription}
        else
          {:error, response.body["error"]}
        end

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

  @spec get_invoices(Plausible.Billing.Subscription.t() | nil) ::
          {:ok, list()}
          | {:error, :request_failed}
          | {:error, :no_invoices}
  def get_invoices(nil), do: {:error, :no_invoices}
  def get_invoices(%{paddle_subscription_id: nil}), do: {:error, :no_invoices}

  def get_invoices(subscription) do
    config = get_config()

    params = %{
      vendor_id: config[:vendor_id],
      vendor_auth_code: config[:vendor_auth_code],
      subscription_id: subscription.paddle_subscription_id,
      is_paid: 1,
      from: Date.shift(Date.utc_today(), year: -5) |> Calendar.strftime("%Y-%m-%d"),
      to: Date.shift(Date.utc_today(), day: 1) |> Calendar.strftime("%Y-%m-%d")
    }

    with {:ok, %{body: body}} <- HTTPClient.post(invoices_url(), @headers, params),
         true <- Map.get(body, "success"),
         [_ | _] = response <- Map.get(body, "response") do
      Enum.sort(response, fn %{"payout_date" => d1}, %{"payout_date" => d2} ->
        Date.compare(Date.from_iso8601!(d1), Date.from_iso8601!(d2)) == :gt
      end)
      |> then(&{:ok, &1})
    else
      error ->
        Sentry.capture_message("Failed to retrieve invoices from Paddle",
          extra: %{extra: inspect(error), params: params, invoices_url: invoices_url()}
        )

        {:error, :request_failed}
    end
  end

  def fetch_prices([_ | _] = product_ids, customer_ip) do
    params = %{product_ids: Enum.join(product_ids, ","), customer_ip: customer_ip}

    prices_final_url =
      prices_url()
      |> URI.parse()
      |> Map.replace(:query, URI.encode_query(params))
      |> URI.to_string()

    case HTTPClient.impl().get(prices_final_url, @headers, nil) do
      {:ok, %{body: %{"success" => true, "response" => %{"products" => products}}}} ->
        products =
          Enum.into(products, %{}, fn %{
                                        "currency" => currency,
                                        "price" => %{"net" => net_price},
                                        "product_id" => product_id
                                      } ->
            {Integer.to_string(product_id), Money.from_float!(currency, net_price)}
          end)

        {:ok, products}

      {:ok, %{body: body}} ->
        Sentry.capture_message("Paddle API: Unexpected response when fetching prices",
          extra: %{api_response: body, product_ids: product_ids}
        )

        {:error, :api_error}

      {:error, %{reason: reason}} ->
        Sentry.capture_message("Paddle API: Error when fetching prices", extra: %{reason: reason})
        {:error, :api_error}
    end
  end

  def checkout_domain() do
    if Application.get_env(:plausible, :environment) == "staging" do
      "https://sandbox-checkout.paddle.com"
    else
      "https://checkout.paddle.com"
    end
  end

  def vendors_domain() do
    if Application.get_env(:plausible, :environment) == "staging" do
      "https://sandbox-vendors.paddle.com"
    else
      "https://vendors.paddle.com"
    end
  end

  defp get_config() do
    Application.get_env(:plausible, :paddle)
  end

  defp invoices_url() do
    Path.join(vendors_domain(), "/api/2.0/subscription/payments")
  end

  defp preview_update_url() do
    Path.join(vendors_domain(), "/api/2.0/subscription/preview_update")
  end

  defp update_subscription_url() do
    Path.join(vendors_domain(), "/api/2.0/subscription/users/update")
  end

  defp get_subscription_url() do
    Path.join(vendors_domain(), "/api/2.0/subscription/users")
  end

  defp prices_url() do
    Path.join(checkout_domain(), "/api/2.0/prices")
  end
end
