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,
            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
      )

    case Finch.request(finch_req, ExcaltFinch) 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)}
      Naiveical.Extractor.extract_sections_by_tag(icalendar, "VTODO")
    end)

    # |> Enum.sort(fn a, b ->
    #   a = List.first(a)
    #   b = List.first(b)
    #   {_tag, _attr, due_str} = Naiveical.Extractor.extract_contentline_by_tag(a, "DUE")
    #   a_due = Naiveical.Helpers.parse_datetime(due_str)
    #   {_tag, _attr, due_str} = Naiveical.Extractor.extract_contentline_by_tag(b, "DUE")
    #   b_due = Naiveical.Helpers.parse_datetime(due_str)
    #   a_due > b_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
      )

    case Finch.request(finch_req, ExcaltFinch) do
      {:ok,
       %Finch.Response{
         status: 201,
         body: body,
         headers: headers
       }} ->
        etag = Excalt.Helpers.Request.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, body}

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

      {:ok,
       %Finch.Response{
         status: status,
         body: body
       }} ->
        {:error, status, 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
      )

    case Finch.request(finch_req, ExcaltFinch) 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

  @doc """
  Updates a single todo item, given an uid of the todo, the new version, and the etag.
  Will throw an error, if the etag has changed in the meantime.
  (see [RFC 4791, section 7.8.1](https://tools.ietf.org/html/rfc4791#section-7.8.9)).
  """
  @spec update(
          server_url :: String.t(),
          username :: String.t(),
          password :: String.t(),
          calendar_name :: String.t(),
          uuid :: String.t(),
          ical_text :: String.t(),
          etag :: String.t(),
          opts :: keyword()
        ) :: {:ok, [t()]} | {:error, any()}
  def update(server_url, username, password, calendar_name, uuid, ical_text, etag, opts \\ []) 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(
        "PUT",
        req_url <> "/#{uuid}.ical",
        [
          {"Authorization", auth_header_content}
        ],
        req_body
      )

    case Finch.request(finch_req, ExcaltFinch) 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
end