% Licensed under the Apache License, Version 2.0 (the "License"); you may not
% use this file except in compliance with the License. You may obtain a copy of
% the License at
%
%   http://www.apache.org/licenses/LICENSE-2.0
%
% Unless required by applicable law or agreed to in writing, software
% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
% License for the specific language governing permissions and limitations under
% the License.

-module(couch_prometheus_http).

-compile(tuple_calls).

-export([
    start_link/0,
    handle_request/1
]).

-include_lib("couch/include/couch_db.hrl").

start_link() ->
    IP =
        case config:get("prometheus", "bind_address", "any") of
            "any" -> any;
            Else -> Else
        end,
    Port = config:get("prometheus", "port"),
    ok = couch_httpd:validate_bind_address(IP),

    Options = [
        {name, ?MODULE},
        {loop, fun ?MODULE:handle_request/1},
        {ip, IP},
        {port, Port}
    ],
    case mochiweb_http:start(Options) of
        {ok, Pid} ->
            {ok, Pid};
        {error, Reason} ->
            io:format("Failure to start Mochiweb: ~s~n", [Reason]),
            {error, Reason}
    end.

handle_request(MochiReq) ->
    RawUri = MochiReq:get(raw_path),
    {"/" ++ Path, _, _} = mochiweb_util:urlsplit_path(RawUri),
    PathParts = string:tokens(Path, "/"),
    try
        case PathParts of
            ["_node", Node, "_prometheus"] ->
                send_prometheus(MochiReq, Node);
            _ ->
                send_error(MochiReq, 404, <<"not_found">>, <<>>)
        end
    catch
        T:R ->
            Body = list_to_binary(io_lib:format("~p:~p", [T, R])),
            send_error(MochiReq, 500, <<"server_error">>, Body)
    end.

send_prometheus(MochiReq, Node) ->
    Type = "text/plain; version=" ++ couch_prometheus:version(),
    Headers =
        couch_httpd:server_header() ++
            [
                {<<"Content-Type">>, ?l2b(Type)}
            ],
    Body = call_node(Node, couch_prometheus, scrape, []),
    send_resp(MochiReq, 200, Headers, Body).

send_resp(MochiReq, Status, ExtraHeaders, Body) ->
    Headers = couch_httpd:server_header() ++ ExtraHeaders,
    MochiReq:respond({Status, Headers, Body}).

send_error(MochiReq, Code, Error, Reason) ->
    Headers =
        couch_httpd:server_header() ++
            [
                {<<"Content-Type">>, <<"application/json">>}
            ],
    JsonError =
        {[
            {<<"error">>, Error},
            {<<"reason">>, Reason}
        ]},
    Body = ?JSON_ENCODE(JsonError),
    MochiReq:respond({Code, Headers, Body}).

call_node("_local", Mod, Fun, Args) ->
    call_node(node(), Mod, Fun, Args);
call_node(Node0, Mod, Fun, Args) when is_list(Node0) ->
    Node1 =
        try
            list_to_existing_atom(Node0)
        catch
            error:badarg ->
                NoNode = list_to_binary(Node0),
                throw({not_found, <<"no such node: ", NoNode/binary>>})
        end,
    call_node(Node1, Mod, Fun, Args);
call_node(Node, Mod, Fun, Args) when is_atom(Node) ->
    case rpc:call(Node, Mod, Fun, Args) of
        {badrpc, nodedown} ->
            Reason = list_to_binary(io_lib:format("~s is down", [Node])),
            throw({error, {nodedown, Reason}});
        Else ->
            Else
    end.
