lib/excalt/todo.ex

defmodule Excalt.Todo do
  @moduledoc """
  Represents the parsed CalDav reply for todo items.
  """
  @type t :: %__MODULE__{
          icalendar: String.t(),
          url: String.t(),
          etag: String.t()
        }
  defstruct icalendar: nil,
            icalendar: nil,
            url: nil,
            etag: nil

  @doc """
  Fetches the raw xml of the calendars for a user from the CalDav server.
  """
  @spec list_raw(
          server_url :: String.t(),
          username :: String.t(),
          password :: String.t(),
          calendar_name :: String.t()
        ) ::
          {:ok, xml :: String.t()} | {:error, any()}
  def list_raw(server_url, username, password, calendar_name) do
    auth_header_content = "Basic " <> Base.encode64("#{username}:#{password}")

    req_body = Excalt.XML.Builder.todo_list()
    req_url = Excalt.Request.UrlHelper.build_url(server_url, username, calendar_name)

    finch_req =
      Finch.build(
        "REPORT",
        req_url,
        [
          {"Authorization", auth_header_content},
          {"Depth", "1"}
        ],
        req_body
      )

    IO.inspect(finch_req: finch_req)

    case Finch.request(finch_req, ExcaltFinch)
         |> IO.inspect() do
      {:ok,
       %Finch.Response{
         status: 207,
         body: body
       }} ->
        {:ok, body}

      {:ok,
       %Finch.Response{
         status: 412,
         body: body
       }} ->
        {:error, body}

      {:ok,
       %Finch.Response{
         status: 404,
         body: body
       }} ->
        {:error, :not_found}
    end
  end

  @doc """
  Returns the parsed xml of the calendars for a user from the CalDav server.
  """
  @spec list!(
          server_url :: String.t(),
          username :: String.t(),
          password :: String.t(),
          calendar_name :: String.t()
        ) :: [t()]
  def list!(server_url, username, password, calendar_name) do
    {:ok, xml_text} = list_raw(server_url, username, password, calendar_name)
    Excalt.XML.Parser.parse_events!(xml_text)
  end

  @doc """
  Returns the sorted parsed list of events for a period of time, where also the icalendar parts are parsed.
  """
  @spec parsed_list!(
          server_url :: String.t(),
          username :: String.t(),
          password :: String.t(),
          calendar_name :: String.t()
        ) :: [t()]
  def parsed_list!(server_url, username, password, calendar_name) do
    list!(server_url, username, password, calendar_name)
    |> Enum.map(fn e ->
      %Excalt.Event{url: url, etag: etag, icalendar: icalendar} = e

      %Excalt.Event{url: url, etag: etag, icalendar: Exicalend.Parser.from_ical(icalendar)}
    end)
    |> Enum.sort(fn a, b ->
      a_todo = List.first(a.icalendar.todos)
      b_todo = List.first(b.icalendar.todos)
      a_todo.due > b_todo.due
    end)
  end

  @doc """
  Creates a new todo item with the given icalendar text.
  """
  @spec create(
          server_url :: String.t(),
          username :: String.t(),
          password :: String.t(),
          calendar_name :: String.t(),
          ical_text :: String.t(),
          uuid :: String.t()
        ) ::
          {:ok, etag :: String.t() | nil} | {:error, any()}

  def create(server_url, username, password, calendar_name, ical_text, uuid) do
    auth_header_content = "Basic " <> Base.encode64("#{username}:#{password}")

    req_body = ical_text
    req_url = Excalt.Request.UrlHelper.build_url(server_url, username, calendar_name)

    finch_req =
      Finch.build(
        "PUT",
        req_url <> "/#{uuid}.ical",
        [
          {"Authorization", auth_header_content},
          # {"if-match", etag}
          {"If-None-Match", "*"}
        ],
        req_body
      )

    IO.inspect(finch_req: finch_req)

    case Finch.request(finch_req, ExcaltFinch)
         |> IO.inspect() do
      {:ok,
       %Finch.Response{
         status: 201,
         body: body,
         headers: headers
       }} ->
        etag = extract_from_header(headers, "etag")
        {:ok, etag}

      {:ok,
       %Finch.Response{
         status: 204,
         body: body
       }} ->
        {:ok, body}

      {:ok,
       %Finch.Response{
         status: 404,
         body: body
       }} ->
        {:error, :not_found}

      {:ok,
       %Finch.Response{
         status: 412,
         body: body
       }} ->
        {:error, body}
    end
  end

  @doc """
  Deletes a todo item with the given uuid.
  """
  @spec delete(
          server_url :: String.t(),
          username :: String.t(),
          password :: String.t(),
          calendar_name :: String.t(),
          uuid :: String.t()
        ) ::
          {:ok, etag :: String.t() | nil} | {:error, any()}

  def delete(server_url, username, password, calendar_name, uuid) do
    auth_header_content = "Basic " <> Base.encode64("#{username}:#{password}")

    req_body = ""
    req_url = Excalt.Request.UrlHelper.build_url(server_url, username, calendar_name)

    finch_req =
      Finch.build(
        "DELETE",
        req_url <> "/#{uuid}.ical",
        [
          {"Authorization", auth_header_content}
        ],
        req_body
      )

    IO.inspect(finch_req: finch_req)

    case Finch.request(finch_req, ExcaltFinch)
         |> IO.inspect() do
      {:ok,
       %Finch.Response{
         status: 201,
         body: body
       }} ->
        {:ok, body}

      {:ok,
       %Finch.Response{
         status: 204,
         body: body
       }} ->
        {:ok, body}

      {:ok,
       %Finch.Response{
         status: 404,
         body: body
       }} ->
        {:error, :not_found}
    end
  end

  defp extract_from_header(header, field) do
    [{_, value}] =
      Enum.filter(header, fn
        {key, value} ->
          if key == field, do: true, else: false
      end)
      |> IO.inspect()

    value
  end
end