lib/connect/client.ex

defmodule OnePassword.Connect.Client do
  alias OnePassword.Connect.{
    Configuration,
    Vault,
    VaultItem
  }

  def fetch(%Configuration{vaults: vaults} = config) do
    Enum.reduce(vaults, %{}, fn
      {vault_name, items}, acc ->
        vault = fetch_vault(config, vault_name)
        Map.put(acc, vault.name, fetch_items(config, vault, items))

      vault_name, acc ->
        vault = fetch_vault(config, vault_name)
        Map.put(acc, vault.name, fetch_items(config, vault, nil))
    end)
  end

  defp fetch_vault(config, name) do
    query =
      URI.encode_query(%{
        filter: "name eq \"#{name}\""
      })

    url = String.to_charlist(config.base_vault_url) ++ '/v1/vaults?' ++ String.to_charlist(query)

    :httpc.request(:get, {url, headers(config)}, ssl_request_opts(config), [])
    |> unpack_get()
    # |> IO.inspect(label: "Vault fetch")
    |> then(fn
      [vault | _] ->
        Vault.from_raw(vault)

      [] ->
        throw("Vault #{name} not found")
    end)
  end

  defp fetch_items(config, %Vault{id: vault_id} = vault, item_filter) do
    url =
      String.to_charlist(config.base_vault_url) ++
        '/v1/vaults/' ++ String.to_charlist(vault_id) ++ '/items'

    :httpc.request(:get, {url, headers(config)}, ssl_request_opts(config), [])
    |> unpack_get()
    # |> IO.inspect(label: "Item list fetch")
    |> filter_items(item_filter)
    |> Enum.reduce(vault, fn raw_item, %Vault{items: items} = acc ->
      acc_items = [
        fetch_item_data(config, vault, raw_item["id"])
        | items
      ]

      %{acc | items: acc_items}
    end)
  end

  defp filter_items(raw_items, nil), do: raw_items

  defp filter_items(raw_items, item_filter) do
    Enum.filter(raw_items, fn %{"title" => title} -> title in item_filter end)
  end

  defp fetch_item_data(config, %Vault{id: vault_id}, item_id) do
    url =
      String.to_charlist(config.base_vault_url) ++
        '/v1/vaults/' ++ String.to_charlist(vault_id) ++ '/items/' ++ String.to_charlist(item_id)

    :httpc.request(:get, {url, headers(config)}, ssl_request_opts(config), [])
    |> unpack_get()
    # |> IO.inspect(label: "Item details fetch")
    |> VaultItem.from_raw()
  end

  defp headers(%Configuration{token: token}) do
    [
      {
        'authorization',
        String.to_charlist("Bearer #{token}")
      },
      {'accept', 'application/json'}
    ]
  end

  defp ssl_request_opts(%Configuration{skip_ssl_check: false}) do
    [ssl: :httpc.ssl_verify_host_options(false)]
  end

  defp ssl_request_opts(_), do: []

  def unpack_get({:ok, {{'HTTP/1.1', 200, 'OK'}, _headers, json_payload}}) do
    Jason.decode!(json_payload)
  end

  def unpack_get(res) do
    require Logger
    Logger.error("Unknown result: #{inspect(res, pretty: true)}")
    throw("Unknown error when fetching")
  end
end