defmodule Plausible.Billing.DevSubscriptions do
  @moduledoc """
  Module for conveniently handling subscriptions in the :dev environment.
  """
  use Plausible

  on_ee do
    import Ecto.Query

    alias Plausible.{Repo, Billing}
    alias Plausible.Billing.{Plan, EnterprisePlan, DevPaddleApiMock}
    alias PlausibleWeb.Router.Helpers, as: Routes

    def create_after_1s(team_id, plan_id) do
      Task.start(fn ->
        Process.sleep(1000)
        create(team_id, plan_id)
      end)
    end

    def create(team_id, plan_id, opts \\ []) do
      plan_or_enterprise_plan = get_plan_or_enterprise_plan(plan_id)

      if plan_or_enterprise_plan do
        if Keyword.get(opts, :clean_existing, true) do
          delete(team_id)
        end

        %{
          "event_time" => to_string(NaiveDateTime.utc_now(:second)),
          "alert_name" => "subscription_created",
          "passthrough" => "ee:true;user:0;team:#{team_id}",
          "email" => "",
          "subscription_id" => Ecto.UUID.generate(),
          "subscription_plan_id" => plan_id,
          "update_url" => Routes.dev_subscription_path(PlausibleWeb.Endpoint, :update_form),
          "cancel_url" => Routes.dev_subscription_path(PlausibleWeb.Endpoint, :cancel_form),
          "status" => "active",
          "next_bill_date" => next_bill_date(plan_or_enterprise_plan, plan_id),
          "unit_price" => "#{to_string(DevPaddleApiMock.all_prices()[plan_id])}.00",
          "currency" => "EUR"
        }
        |> Plausible.Billing.subscription_created()

        {:ok, Repo.reload(get_team_subscription(team_id))}
      else
        {:error, "Plan with id '#{plan_id}' not found."}
      end
    end

    def update(team_id, new_status) do
      if subscription = get_team_subscription(team_id) do
        current_plan_id = subscription.paddle_plan_id
        current_plan = get_plan_or_enterprise_plan(current_plan_id)

        next_bill_date =
          case new_status do
            "active" -> next_bill_date(current_plan, current_plan_id)
            "past_due" -> Date.utc_today() |> Date.shift(day: 5) |> Date.to_iso8601()
            "paused" -> subscription.next_bill_date |> Date.to_iso8601()
          end

        %{
          "subscription_id" => subscription.paddle_subscription_id,
          "subscription_plan_id" => current_plan_id,
          "update_url" => subscription.update_url,
          "cancel_url" => subscription.cancel_url,
          "old_status" => to_string(subscription.status),
          "status" => new_status,
          "next_bill_date" => next_bill_date,
          "new_unit_price" => "#{to_string(DevPaddleApiMock.all_prices()[current_plan_id])}.00",
          "currency" => "EUR"
        }
        |> Plausible.Billing.subscription_updated()

        :ok
      else
        {:error, :no_subscription}
      end
    end

    def delete(team_id, opts \\ []) do
      delete_enterprise? = Keyword.get(opts, :delete_enterprise?, false)

      if subscription = get_team_subscription(team_id) do
        Repo.delete(subscription)

        if delete_enterprise? do
          Plausible.Repo.delete_all(from(e in EnterprisePlan, where: e.team_id == ^team_id))
        end

        :ok
      else
        {:error, :no_subscription}
      end
    end

    def cancel(team_id, opts \\ []) do
      set_expired? = Keyword.get(opts, :set_expired?, false)

      if subscription = get_team_subscription(team_id) do
        %{"subscription_id" => subscription.paddle_subscription_id, "status" => "deleted"}
        |> Billing.subscription_cancelled()

        subscription = Repo.reload!(subscription)

        if set_expired? do
          subscription
          |> Ecto.Changeset.change(%{next_bill_date: Date.utc_today() |> Date.add(-1)})
          |> Repo.update()
        else
          {:ok, subscription}
        end
      else
        {:error, :no_subscription}
      end
    end

    defp get_plan_or_enterprise_plan(plan_id) do
      Billing.Plans.find(plan_id) || Repo.get_by(EnterprisePlan, paddle_plan_id: plan_id)
    end

    defp get_team_subscription(team_id) do
      Plausible.Teams.Team
      |> Plausible.Repo.get(team_id)
      |> Plausible.Teams.with_subscription()
      |> Map.get(:subscription)
    end

    defp next_bill_date(%EnterprisePlan{} = plan, _) do
      if plan.billing_interval == :monthly, do: next_month(), else: next_year()
    end

    defp next_bill_date(%Plan{} = plan, plan_id) do
      if plan_id == plan.monthly_product_id, do: next_month(), else: next_year()
    end

    defp next_month(), do: Date.utc_today() |> Date.shift(month: 1) |> Date.to_iso8601()
    defp next_year(), do: Date.utc_today() |> Date.shift(year: 1) |> Date.to_iso8601()
  end
end
