defmodule Swoosh.Adapters.MailerSend do
  @moduledoc """
  Swoosh adapter for the MailerSend API.

  Requires `api_key` in config. Uses the v1 email endpoint.
  """

  use Swoosh.Adapter, required_config: [:api_key]

  alias Swoosh.Email

  @base_url "https://api.mailersend.com"
  @api_endpoint "/v1/email"

  def deliver(%Email{} = email, config \\ []) do
    headers = [
      {"Authorization", "Bearer #{config[:api_key]}"},
      {"Content-Type", "application/json"},
      {"User-Agent", "swoosh/#{Swoosh.version()}"}
    ]

    body = email |> prepare_body() |> Swoosh.json_library().encode!()
    url = [config[:base_url] || @base_url, @api_endpoint]

    case Swoosh.ApiClient.post(url, headers, body, email) do
      {:ok, code, _headers, body} when code >= 200 and code <= 299 ->
        {:ok, decode_success(body)}

      {:ok, code, _headers, body} ->
        case Swoosh.json_library().decode(body) do
          {:ok, error} -> {:error, {code, error}}
          {:error, _} -> {:error, {code, body}}
        end

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

  defp decode_success(""), do: %{}

  defp decode_success(body) do
    case Swoosh.json_library().decode(body) do
      {:ok, %{"message_id" => id}} -> %{id: id}
      {:ok, parsed} -> parsed
      _ -> %{}
    end
  end

  defp prepare_body(email) do
    %{}
    |> prepare_from(email)
    |> prepare_to(email)
    |> prepare_cc(email)
    |> prepare_bcc(email)
    |> prepare_reply_to(email)
    |> prepare_subject(email)
    |> prepare_text(email)
    |> prepare_html(email)
  end

  defp prepare_from(body, %{from: {name, address}}) when name not in [nil, ""] do
    Map.put(body, "from", %{"email" => address, "name" => name})
  end

  defp prepare_from(body, %{from: {_name, address}}) do
    Map.put(body, "from", %{"email" => address})
  end

  defp prepare_to(body, %{to: to}) do
    Map.put(body, "to", Enum.map(to, &format_recipient/1))
  end

  defp prepare_cc(body, %{cc: []}), do: body

  defp prepare_cc(body, %{cc: cc}) do
    Map.put(body, "cc", Enum.map(cc, &format_recipient/1))
  end

  defp prepare_bcc(body, %{bcc: []}), do: body

  defp prepare_bcc(body, %{bcc: bcc}) do
    Map.put(body, "bcc", Enum.map(bcc, &format_recipient/1))
  end

  defp prepare_reply_to(body, %{reply_to: nil}), do: body

  defp prepare_reply_to(body, %{reply_to: {name, address}}) when name not in [nil, ""] do
    Map.put(body, "reply_to", %{"email" => address, "name" => name})
  end

  defp prepare_reply_to(body, %{reply_to: {_name, address}}) do
    Map.put(body, "reply_to", %{"email" => address})
  end

  defp prepare_subject(body, %{subject: subject}) when subject not in [nil, ""] do
    Map.put(body, "subject", subject)
  end

  defp prepare_subject(body, _), do: body

  defp prepare_text(body, %{text_body: nil}), do: body
  defp prepare_text(body, %{text_body: text}), do: Map.put(body, "text", text)

  defp prepare_html(body, %{html_body: nil}), do: body
  defp prepare_html(body, %{html_body: html}), do: Map.put(body, "html", html)

  defp format_recipient({name, email}) when name not in [nil, ""] do
    %{"email" => email, "name" => name}
  end

  defp format_recipient({_name, email}), do: %{"email" => email}
end
