defmodule PlugIndie do
  @moduledoc """
  A Plug for building a Micropub server.

  To use:

  """
  require Logger
  use Plug.Router
  alias PlugIndie.{Parser, Handler, Response}

  plug :match
  plug :dispatch

  @default_scopes [
    "create",
    "media"
  ]

  # Plug Callbacks

  @doc false
  def init(opts) do
    hostname =
      Keyword.get(opts, :hostname) ||
        raise ArgumentError, "Micropub Plug requires :hostname option"

    handler =
      Keyword.get(opts, :handler) || raise ArgumentError, "Micropub Plug requires :handler option"

    token_endpoint =
      Keyword.get(opts, :token_endpoint) ||
        raise ArgumentError, "Micropub Plug requires :token_endpoint option"

    json_encoder =
      Keyword.get(opts, :json_encoder) ||
        raise ArgumentError, "Micropub Plug requires :json_encoder option"

    user_agent =
      Keyword.get(opts, :user_agent) ||
        raise ArgumentError, "Micropub Plug requires :user_agent option"

    scopes =
      Keyword.get(opts, :scopes) || @default_scopes

    token_handler =
      Keyword.get(opts, :token_handler) || PlugIndie.Token

    [
      hostname: hostname,
      handler: handler,
      token_handler: token_handler,
      json_encoder: json_encoder,
      scopes: scopes,
      token_endpoint: token_endpoint,
      user_agent: user_agent
    ]
  end

  @doc false
  def call(conn, opts) do
    conn = put_private(conn, :plug_micropub, opts)
    super(conn, opts)
  end

  # Routes

  post "/" do
    token_endpoint = get_config(conn, :token_endpoint)
    supported_scopes = get_config(conn, :scopes)
    hostname = get_config(conn, :hostname)
    user_agent = get_config(conn, :user_agent)
    token_handler = get_config(conn, :token_handler)

    with {:ok, access_token, conn} <- Parser.get_access_token(conn),
         {:ok, action, conn} <- Parser.get_action(conn),
         :ok <-
           token_handler.verify(
             access_token,
             token_endpoint,
             Atom.to_string(action),
             supported_scopes,
             hostname,
             user_agent
           ) do
      Handler.handle_action(action, access_token, conn)
    else
      error -> Response.send_error(conn, error)
    end
  end

  get "/" do
    token_endpoint = get_config(conn, :token_endpoint)
    supported_scopes = get_config(conn, :scopes)
    hostname = get_config(conn, :hostname)
    user_agent = get_config(conn, :user_agent)
    token_handler = get_config(conn, :token_handler)

    with {:ok, access_token, conn} <- Parser.get_access_token(conn),
         {:ok, query} <- Parser.get_query(conn),
         :ok <-
           token_handler.verify(
             access_token,
             token_endpoint,
             "source",
             supported_scopes,
             hostname,
             user_agent
           ) do
      Handler.handle_query(query, access_token, conn)
    else
      error -> Response.send_error(conn, error)
    end
  end

  post "/media" do
    handler = get_config(conn, :handler)
    token_endpoint = get_config(conn, :token_endpoint)
    supported_scopes = get_config(conn, :scopes)
    hostname = get_config(conn, :hostname)
    user_agent = get_config(conn, :user_agent)
    token_handler = get_config(conn, :token_handler)

    with {:ok, access_token, conn} <- Parser.get_access_token(conn),
         {:ok, file} <- Parser.get_file(conn),
         {:ok, url} <- handler.handle_media(file, access_token),
         :ok <-
           token_handler.verify(
             access_token,
             token_endpoint,
             "media",
             supported_scopes,
             hostname,
             user_agent
           ) do
      conn
      |> put_resp_header("location", url)
      |> send_resp(:created, "")
    else
      error -> Response.send_error(conn, error)
    end
  end

  match _ do
    Response.send_error(conn, {:error, :invalid_request, "Request did not match any route."})
  end

  defp get_config(conn, name) do
    conn.private[:plug_micropub][name]
  end
end