defmodule Blogex.Feed do
  @moduledoc """
  Generates RSS 2.0 and Atom feeds for a blog.

  ## Usage

      # In a controller or plug:
      xml = Blogex.Feed.rss(MyApp.EngineeringBlog, "https://myapp.com")
      conn |> put_resp_content_type("application/rss+xml") |> send_resp(200, xml)
  """

  @doc """
  Generates an RSS 2.0 XML feed for the given blog module.

  ## Options

    * `:limit` - max number of posts to include (default: 20)
    * `:language` - feed language (default: "en-us")
  """
  def rss(blog_module, base_url, opts \\ []) do
    limit = Keyword.get(opts, :limit, 20)
    language = Keyword.get(opts, :language, "en-us")
    posts = Enum.take(blog_module.all_posts(), limit)

    blog_url = "#{base_url}#{blog_module.base_path()}"
    feed_url = "#{blog_url}/feed.xml"

    pub_date =
      case posts do
        [latest | _] -> format_rfc822(latest.date)
        [] -> format_rfc822(Date.utc_today())
      end

    """
    <?xml version="1.0" encoding="UTF-8"?>
    <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
      <channel>
        <title>#{escape(blog_module.title())}</title>
        <link>#{blog_url}</link>
        <description>#{escape(blog_module.description())}</description>
        <language>#{language}</language>
        <pubDate>#{pub_date}</pubDate>
        <atom:link href="#{feed_url}" rel="self" type="application/rss+xml"/>
    #{Enum.map_join(posts, "\n", &item_xml(&1, base_url, blog_module))}
      </channel>
    </rss>
    """
    |> String.trim()
  end

  @doc """
  Generates an Atom feed for the given blog module.

  ## Options

    * `:limit` - max number of posts to include (default: 20)
  """
  def atom(blog_module, base_url, opts \\ []) do
    limit = Keyword.get(opts, :limit, 20)
    posts = Enum.take(blog_module.all_posts(), limit)

    blog_url = "#{base_url}#{blog_module.base_path()}"
    feed_url = "#{blog_url}/feed.xml"

    updated =
      case posts do
        [latest | _] -> format_iso8601(latest.date)
        [] -> format_iso8601(Date.utc_today())
      end

    """
    <?xml version="1.0" encoding="UTF-8"?>
    <feed xmlns="http://www.w3.org/2005/Atom">
      <title>#{escape(blog_module.title())}</title>
      <link href="#{blog_url}" rel="alternate"/>
      <link href="#{feed_url}" rel="self"/>
      <id>#{blog_url}</id>
      <updated>#{updated}</updated>
    #{Enum.map_join(posts, "\n", &entry_xml(&1, base_url, blog_module))}
    </feed>
    """
    |> String.trim()
  end

  # Private helpers

  defp item_xml(post, base_url, blog_module) do
    url = "#{base_url}#{blog_module.base_path()}/#{post.id}"

    """
        <item>
          <title>#{escape(post.title)}</title>
          <link>#{url}</link>
          <guid isPermaLink="true">#{url}</guid>
          <pubDate>#{format_rfc822(post.date)}</pubDate>
          <description>#{escape(post.description)}</description>
          <content:encoded><![CDATA[#{post.body}]]></content:encoded>
    #{Enum.map_join(post.tags, "\n", &"      <category>#{escape(&1)}</category>")}
        </item>
    """
  end

  defp entry_xml(post, base_url, blog_module) do
    url = "#{base_url}#{blog_module.base_path()}/#{post.id}"

    """
        <entry>
          <title>#{escape(post.title)}</title>
          <link href="#{url}" rel="alternate"/>
          <id>#{url}</id>
          <published>#{format_iso8601(post.date)}</published>
          <updated>#{format_iso8601(post.date)}</updated>
          <author><name>#{escape(post.author)}</name></author>
          <summary>#{escape(post.description)}</summary>
          <content type="html"><![CDATA[#{post.body}]]></content>
    #{Enum.map_join(post.tags, "\n", &"      <category term=\"#{escape(&1)}\"/>")}
        </entry>
    """
  end

  defp format_rfc822(date) do
    date
    |> DateTime.new!(~T[00:00:00], "Etc/UTC")
    |> Calendar.strftime("%a, %d %b %Y %H:%M:%S +0000")
  end

  defp format_iso8601(date) do
    "#{Date.to_iso8601(date)}T00:00:00Z"
  end

  defp escape(text) when is_binary(text) do
    text
    |> String.replace("&", "&amp;")
    |> String.replace("<", "&lt;")
    |> String.replace(">", "&gt;")
    |> String.replace("\"", "&quot;")
    |> String.replace("'", "&apos;")
  end
end
