defmodule Kraken.Api.Router do
  use Plug.Router

  alias Kraken.Api.{Events, Pipelines, Routes, Services}
  alias Kraken.Utils

  plug(Plug.Logger, log: :debug)
  plug(:match)
  plug(:dispatch)

  get "/" do
    send_resp(conn, 200, "Hey! I'm Kraken!")
  end

  get "/favicon.ico" do
    send_resp(conn, 200, "sorry, no icon")
  end

  # Services
  post "/services/define" do
    {:ok, body, conn} = read_body(conn)

    case Services.define(body) do
      {:ok, response} ->
        send_resp(conn, 200, response)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  get "/services" do
    {:ok, response} = Services.services()
    send_resp(conn, 200, response)
  end

  post "/services/start/:name" do
    {:ok, body, conn} = read_body(conn)

    case Services.start(conn.params["name"], body) do
      {:ok, response} ->
        send_resp(conn, 200, response)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  post "/services/call/:name/:function" do
    {:ok, body, conn} = read_body(conn)

    case Services.call(conn.params["name"], conn.params["function"], body) do
      {:ok, response} ->
        send_resp(conn, 200, response)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  post "/services/stop/:name" do
    {:ok, body, conn} = read_body(conn)

    case Services.stop(conn.params["name"], body) do
      {:ok, response} ->
        send_resp(conn, 200, response)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  post "/services/delete/:name" do
    case Services.delete(conn.params["name"]) do
      {:ok, response} ->
        send_resp(conn, 200, response)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  get "/services/status/:name" do
    {:ok, response} = Services.status(conn.params["name"])
    send_resp(conn, 200, response)
  end

  get "/services/definition/:name" do
    case Services.definition(conn.params["name"]) do
      {:ok, response} ->
        send_resp(conn, 200, response)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  get "/services/state/:name" do
    case Services.state(conn.params["name"]) do
      {:ok, response} ->
        send_resp(conn, 200, response)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  # Pipelines
  post "/pipelines/define" do
    {:ok, body, conn} = read_body(conn)

    case Pipelines.define(body) do
      {:ok, response} ->
        send_resp(conn, 200, response)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  get "/pipelines" do
    {:ok, response} = Pipelines.pipelines()
    send_resp(conn, 200, response)
  end

  get "/pipelines/status/:name" do
    {:ok, response} = Pipelines.status(conn.params["name"])
    send_resp(conn, 200, response)
  end

  get "/pipelines/definition/:name" do
    case Pipelines.definition(conn.params["name"]) do
      {:ok, response} ->
        send_resp(conn, 200, response)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  post "/pipelines/start/:name" do
    {:ok, body, conn} = read_body(conn)
    conn = fetch_query_params(conn)

    case Pipelines.start(conn.params, body) do
      {:ok, response} ->
        send_resp(conn, 200, response)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  post "/pipelines/stop/:name" do
    case Pipelines.stop(conn.params["name"]) do
      {:ok, response} ->
        send_resp(conn, 200, response)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  post "/pipelines/delete/:name" do
    case Pipelines.delete(conn.params["name"]) do
      {:ok, response} ->
        send_resp(conn, 200, response)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  post "/pipelines/call/:name" do
    {:ok, body, conn} = read_body(conn)
    conn = fetch_query_params(conn)

    case Pipelines.call(conn.params, body) do
      {:ok, response} ->
        send_resp(conn, 200, response)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  post "/pipelines/cast/:name" do
    {:ok, body, conn} = read_body(conn)
    conn = fetch_query_params(conn)

    case Pipelines.cast(conn.params, body) do
      {:ok, response} ->
        send_resp(conn, 200, response)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  post "/pipelines/stream/:name" do
    {:ok, body, conn} = read_body(conn)
    conn = fetch_query_params(conn)

    case Pipelines.stream(conn.params, body) do
      {:ok, stream} ->
        conn = send_chunked(conn, 200)

        Enum.reduce_while(stream, conn, fn event, conn ->
          chunk =
            event
            |> Utils.struct_to_map()
            |> Jason.encode!()

          case Plug.Conn.chunk(conn, chunk) do
            {:ok, conn} ->
              {:cont, conn}

            {:error, :closed} ->
              {:halt, conn}
          end
        end)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  # Routes
  post "/routes/define" do
    {:ok, body, conn} = read_body(conn)

    case Routes.define(body) do
      {:ok, response} ->
        send_resp(conn, 200, response)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  get "/routes" do
    case Routes.all() do
      {:ok, response} ->
        send_resp(conn, 200, response)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  # Events
  post "/call" do
    {:ok, body, conn} = read_body(conn)
    conn = fetch_query_params(conn)

    case Events.call(conn.params, body) do
      {:ok, response} ->
        send_resp(conn, 200, response)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  post "/cast" do
    {:ok, body, conn} = read_body(conn)
    conn = fetch_query_params(conn)

    case Events.cast(conn.params, body) do
      {:ok, response} ->
        send_resp(conn, 200, response)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  post "/stream" do
    {:ok, body, conn} = read_body(conn)
    conn = fetch_query_params(conn)

    case Events.stream(conn.params, body) do
      {:ok, stream} ->
        conn = send_chunked(conn, 200)

        Enum.reduce_while(stream, conn, fn event, conn ->
          chunk =
            event
            |> Utils.struct_to_map()
            |> Jason.encode!()

          case Plug.Conn.chunk(conn, chunk) do
            {:ok, conn} ->
              {:cont, conn}

            {:error, :closed} ->
              {:halt, conn}
          end
        end)

      {:error, response} ->
        send_resp(conn, 400, response)
    end
  end

  match _ do
    send_resp(conn, 404, "NOT FOUND")
  end
end
