defmodule AsciinemaWeb.Api.AsciicastController do
  use AsciinemaWeb, :controller
  import AsciinemaWeb.Auth, only: [get_basic_auth: 1, put_basic_auth: 3]
  alias Asciinema.{Asciicasts, Users}
  alias Asciinema.Users.User

  plug :parse_v0_params
  plug :authenticate

  def create(conn, %{"asciicast" => %Plug.Upload{} = upload}) do
    do_create(conn, upload)
  end
  def create(conn, %{"asciicast" => %{"meta" => %{},
                                      "stdout" => %Plug.Upload{},
                                      "stdout_timing" => %Plug.Upload{}} = asciicast_params}) do
    do_create(conn, asciicast_params)
  end

  defp do_create(conn, params) do
    user = conn.assigns.current_user
    user_agent = conn |> get_req_header("user-agent") |> List.first

    case Asciicasts.create_asciicast(user, params, %{user_agent: user_agent}) do
      {:ok, asciicast} ->
        url = asciicast_url(conn, :show, asciicast)
        conn
        |> put_status(:created)
        |> put_resp_header("location", url)
        |> text(url)
      {:error, :parse_error} ->
        conn
        |> put_status(:bad_request)
        |> text("This doesn't look like a valid asciicast file")
      {:error, :unknown_format} ->
        conn
        |> put_status(:unsupported_media_type)
        |> text("Format not supported")
      {:error, %Ecto.Changeset{} = changeset} ->
        conn
        |> put_status(:unprocessable_entity)
        |> render("error.json", changeset: changeset)
    end
  end

  defp parse_v0_params(%Plug.Conn{params: %{"asciicast" => %{"meta" => %Plug.Upload{path: meta_path}}}} = conn, _) do
    with {:ok, json} <- File.read(meta_path),
         {:ok, attrs} <- Poison.decode(json) do
      conn
      |> put_param(["asciicast", "meta"], Map.put(attrs, "version", 0))
      |> put_basic_auth(attrs["username"], attrs["user_token"])
    else
      {:error, :invalid} ->
        send_resp(conn, 400, "")
    end
  end
  defp parse_v0_params(conn, _), do: conn

  defp put_param(%Plug.Conn{params: params} = conn, path, value) do
    params = put_in(params, path, value)
    %{conn | params: params}
  end

  defp authenticate(conn, _opts) do
    with {username, api_token} <- get_basic_auth(conn),
         {:ok, %User{} = user} <- Users.get_user_with_api_token(api_token, username) do
      assign(conn, :current_user, user)
    else
      _otherwise ->
        conn
        |> send_resp(401, "Invalid or revoked recorder token")
        |> halt()
    end
  end
end
