lib/plaid/asset_report.ex

defmodule Plaid.AssetReport do
  @moduledoc """
  [Plaid Asset Reports API](https://plaid.com/docs/api/products/#assets) calls and schema.
  """

  alias Plaid.AssetReport.User
  alias Plaid.Castable

  defmodule AsyncResponse do
    @moduledoc """
    Plaid Asset Report schema used when generating asset reports.

    Async because it only returns a token, the actual asset-report needs to
    be fetched after the proper webhook is received. [See docs.](https://plaid.com/docs/api/products/#asset_reportcreate)
    """

    @behaviour Castable

    @type t :: %__MODULE__{
            asset_report_token: String.t(),
            asset_report_id: String.t(),
            request_id: String.t()
          }

    defstruct [
      :asset_report_token,
      :asset_report_id,
      :request_id
    ]

    @impl true
    def cast(generic_map) do
      %__MODULE__{
        asset_report_token: generic_map["asset_report_token"],
        asset_report_id: generic_map["asset_report_id"],
        request_id: generic_map["request_id"]
      }
    end
  end

  @doc """
  Create an Asset Report.

  Does a `POST /asset_report/create` call to initiate the process of
  creating an asset report.

  Params:
  * `access_tokens` - List of tokens coresponding to the items that will be included in the report.
  * `days_requested` - The days of history to include in the asset report.

  Options:
  * `:client_report_id` - Client-generated identifier, which can be used by lenders to track loan applications.
  * `:webhook` - URL Plaid will use to send webhooks for when the asset report is ready.
  * `:user` - Information about the user to be appended to the asset report. See `Plaid.AssetReport.User` for available fields.

  ## Examples

      AssetReport.create(["access-sandbox-123xxx"], 3, client_id: "123", secret: "abc")
      {:ok, %AssetReport.AsyncResponse{}}

  """
  @spec create([String.t()], non_neg_integer(), options, Plaid.config()) ::
          {:ok, AsyncResponse.t()} | {:error, Plaid.Error.t()}
        when options: %{
               optional(:client_report_id) => String.t(),
               optional(:webhook) => String.t(),
               optional(:user) => User.t()
             }
  def create(access_tokens, days_requested, options \\ %{}, config) do
    options_payload = Map.take(options, [:client_report_id, :webhook, :user])

    payload =
      %{}
      |> Map.put(:access_tokens, access_tokens)
      |> Map.put(:days_requested, days_requested)
      |> Map.put(:options, options_payload)

    Plaid.Client.call("/asset_report/create", payload, AsyncResponse, config)
  end

  defmodule GetResponse do
    @moduledoc """
    [Plaid API /asset_report/get response schema](https://plaid.com/docs/api/products/#asset_reportget)
    """

    @behaviour Castable

    alias Plaid.AssetReport.{Report, Warning}

    @type t :: %__MODULE__{
            report: Report.t(),
            warnings: [Warning.t()],
            request_id: String.t()
          }

    defstruct [
      :report,
      :warnings,
      :request_id
    ]

    @impl true
    def cast(generic_map) do
      %__MODULE__{
        report: Castable.cast(Report, generic_map["report"]),
        warnings: Castable.cast_list(Warning, generic_map["warnings"]),
        request_id: generic_map["request_id"]
      }
    end
  end

  @doc """
  Get an asset report.

  Does a `POST /asset_report/get` call to fetch an asset report.

  Params:
  * `asset_report_token` - The asset report token from the `create_report` response.

  Options: 
  * `:include_insights` - Whether we should retrieve the report as an "Assets + Insights" report.

  ## Examples

      AssetReport.get("asset-prod-123xxx", client_id: "123", secret: "abc")
      {:ok, %AssetReport.GetResponse{}}

  """
  @spec get(String.t(), options, Plaid.config()) ::
          {:ok, GetResponse.t()} | {:error, Plaid.Error.t()}
        when options: %{optional(:include_insights) => boolean()}
  def get(asset_report_token, options \\ %{}, config) do
    payload =
      options
      |> Map.take([:include_insights])
      |> Map.put(:asset_report_token, asset_report_token)

    Plaid.Client.call("/asset_report/get", payload, GetResponse, config)
  end

  @doc """
  Get an asset report as a PDF.

  Does a `POST /asset_report/pdf/get` call to fetch an asset report as a PDF.

  Params:
  * `asset_report_token` - The asset report token from the `create_report` response.

  ## Examples

      AssetReport.get_pdf("asset-prod-123xxx", client_id: "123", secret: "abc")
      {:ok, <<0, 1, 2, 3>>}

  """
  @spec get_pdf(String.t(), Plaid.config()) :: {:ok, bitstring()} | {:error, Plaid.Error.t()}
  def get_pdf(asset_report_token, config) do
    Plaid.Client.call(
      "/asset_report/pdf/get",
      %{asset_report_token: asset_report_token},
      :raw,
      config
    )
  end

  @doc """
  Create a new, refreshed Asset Report.

  Does a `POST /asset_report/refresh` call to initiate the process of
  creating a new asset report with refreshed data, based off of a previous one.

  Params:
  * `asset_report_token` - The token for the asset report you want to refresh.

  Options:
  * `:client_report_id` - Client-generated identifier, which can be used by lenders to track loan applications.
  * `:days_requested` - The days of history to include in the asset report.
  * `:webhook` - URL Plaid will use to send webhooks for when the asset report is ready.
  * `:user` - Information about the user to be appended to the asset report. See `Plaid.AssetReport.User` for available fields.

  Each option above acts as an "override" of the original values passed to `create/4`.
  Meaning when not specified, values from the original `create/4` request will be used.

  ## Examples

      AssetReport.refresh("assets-sandbox-123xxx", client_id: "123", secret: "abc")
      {:ok, %AssetReport.AsyncResponse{}}

  """
  @spec refresh(String.t(), options, Plaid.config()) ::
          {:ok, AsyncResponse.t()} | {:error, Plaid.Error.t()}
        when options: %{
               optional(:client_report_id) => String.t(),
               optional(:days_requested) => non_neg_integer(),
               optional(:webhook) => String.t(),
               optional(:user) => User.t()
             }
  def refresh(asset_report_token, options \\ %{}, config) do
    options_payload = Map.take(options, [:client_report_id, :webhook, :user])

    payload =
      %{}
      |> Plaid.Util.maybe_put(:days_requested, options)
      |> Map.put(:asset_report_token, asset_report_token)
      |> Map.put(:options, options_payload)

    Plaid.Client.call("/asset_report/refresh", payload, AsyncResponse, config)
  end

  @doc """
  Create a new, filtered Asset Report.

  Does a `POST /asset_report/filter` call to initiate the process of
  creating a new asset report with filtered accounts, based off of a previous one.

  Params:
  * `asset_report_token` - The token for the asset report you want to refresh.
  * `account_ids_to_exclude` - The accounts to exclude from the original Asset Report.

  ## Examples

      AssetReport.filter("assets-sandbox-123xxx", ["123xxx"], client_id: "123", secret: "abc")
      {:ok, %AssetReport.AsyncResponse{}}

  """
  @spec filter(String.t(), [String.t()], Plaid.config()) ::
          {:ok, AsyncResponse.t()} | {:error, Plaid.Error.t()}
  def filter(asset_report_token, account_ids_to_exclude, config) do
    Plaid.Client.call(
      "/asset_report/filter",
      %{
        asset_report_token: asset_report_token,
        account_ids_to_exclude: account_ids_to_exclude
      },
      AsyncResponse,
      config
    )
  end

  defmodule RemoveResponse do
    @moduledoc """
    [Plaid /asset_report/remove response schema.](https://plaid.com/docs/api/products/#asset_reportremove)
    """

    @behaviour Castable

    @type t :: %__MODULE__{
            removed: boolean(),
            request_id: String.t()
          }

    defstruct [
      :removed,
      :request_id
    ]

    @impl true
    def cast(generic_map) do
      %__MODULE__{
        removed: generic_map["removed"],
        request_id: generic_map["request_id"]
      }
    end
  end

  @doc """
  Remove an Asset Report.

  Does a `POST /asset_report/remove` call to remove an asset report and
  invalidate its `asset_report_token`.

  Params:
  * `asset_report_token` - The token for the asset report you want to remove.

  ## Examples

      AssetReport.remove("assets-sandbox-123xxx", client_id: "123", secret: "abc")
      {:ok, %AssetReport.RemoveResponse{}}

  """
  @spec remove(String.t(), Plaid.config()) ::
          {:ok, RemoveResponse.t()} | {:error, Plaid.Error.t()}
  def remove(asset_report_token, config) do
    Plaid.Client.call(
      "/asset_report/remove",
      %{asset_report_token: asset_report_token},
      RemoveResponse,
      config
    )
  end

  defmodule CreateAuditCopyResponse do
    @moduledoc """
    [Plaid /asset_report/audit_copy/create response schema](https://plaid.com/docs/api/products/#asset_reportaudit_copycreate)
    """

    @behaviour Castable

    @type t :: %__MODULE__{
            audit_copy_token: String.t(),
            request_id: String.t()
          }

    defstruct [
      :audit_copy_token,
      :request_id
    ]

    @impl true
    def cast(generic_map) do
      %__MODULE__{
        audit_copy_token: generic_map["audit_copy_token"],
        request_id: generic_map["request_id"]
      }
    end
  end

  @doc """
  Create an audit copy token.

  Does a `POST /asset_report/audit_copy/create` call to create an audit copy token
  which can be sent to participating auditors.

  Params:
  * `asset_report_token` - The token for which you want to create an audit copy.
  * `auditor_id` - The auditor_id of the third party with whom you would like to share the Asset Report.

  ## Examples

      AssetReport.create_audit_copy("assets-sandbox-123xxx", "fannie_mae", client_id: "123", secret: "abc")
      {:ok, %AssetReport.CreateAuditCopyResponse{}}

  """
  @spec create_audit_copy(String.t(), String.t(), Plaid.config()) ::
          {:ok, CreateAuditCopyResponse.t()} | {:error, Plaid.Error.t()}
  def create_audit_copy(asset_report_token, auditor_id, config) do
    Plaid.Client.call(
      "/asset_report/audit_copy/create",
      %{asset_report_token: asset_report_token, auditor_id: auditor_id},
      CreateAuditCopyResponse,
      config
    )
  end

  defmodule RemoveAuditCopyResponse do
    @moduledoc """
    [Plaid /asset_report/audit_copy/remove response schema.](https://plaid.com/docs/api/products/#asset_reportaudit_copyremove)
    """

    @behaviour Castable

    @type t :: %__MODULE__{
            removed: boolean(),
            request_id: String.t()
          }

    defstruct [
      :removed,
      :request_id
    ]

    @impl true
    def cast(generic_map) do
      %__MODULE__{
        removed: generic_map["removed"],
        request_id: generic_map["request_id"]
      }
    end
  end

  @doc """
  Remove an Asset Report Audit Copy.

  Does a `POST /asset_report/audit_copy/remove` call to remove an audit copy and
  invalidate its `audit_copy_token`.

  Params:
  * `audit_copy_token` - The token for the asset report audit copy you want to remove.

  ## Examples

      AssetReport.remove_audit_copy("a-sandbox-123xxx", client_id: "123", secret: "abc")
      {:ok, %AssetReport.RemoveAuditCopyResponse{}}

  """
  @spec remove_audit_copy(String.t(), Plaid.config()) ::
          {:ok, RemoveAuditCopyResponse.t()} | {:error, Plaid.Error.t()}
  def remove_audit_copy(audit_copy_token, config) do
    Plaid.Client.call(
      "/asset_report/audit_copy/remove",
      %{audit_copy_token: audit_copy_token},
      RemoveAuditCopyResponse,
      config
    )
  end
end