% 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(setup_httpd).
-include_lib("couch/include/couch_db.hrl").

-export([handle_setup_req/1]).

handle_setup_req(#httpd{method = 'POST'} = Req) ->
    ok = chttpd:verify_is_server_admin(Req),
    couch_httpd:validate_ctype(Req, "application/json"),
    Setup = get_body(Req),
    couch_log:notice("Setup: ~p~n", [couch_util:remove_sensitive_data(Setup)]),
    Action = binary_to_list(couch_util:get_value(<<"action">>, Setup, <<"missing">>)),
    case handle_action(Action, Setup) of
        ok ->
            chttpd:send_json(Req, 201, {[{ok, true}]});
        {error, Message} ->
            couch_httpd:send_error(Req, 400, <<"bad_request">>, Message)
    end;
handle_setup_req(#httpd{method = 'GET'} = Req) ->
    ok = chttpd:verify_is_server_admin(Req),
    Dbs = chttpd:qs_json_value(Req, "ensure_dbs_exist", setup:cluster_system_dbs()),
    couch_log:notice("Dbs: ~p~n", [Dbs]),
    SingleNodeConfig = config:get_boolean("couchdb", "single_node", false),
    case SingleNodeConfig of
        true ->
            chttpd:send_json(Req, 200, {[{state, single_node_enabled}]});
        _ ->
            case config:get_integer("cluster", "n", 3) of
                1 ->
                    case setup:is_single_node_enabled(Dbs) of
                        false ->
                            chttpd:send_json(Req, 200, {[{state, single_node_disabled}]});
                        true ->
                            chttpd:send_json(Req, 200, {[{state, single_node_enabled}]})
                    end;
                _ ->
                    case setup:is_cluster_enabled() of
                        false ->
                            chttpd:send_json(Req, 200, {[{state, cluster_disabled}]});
                        true ->
                            case setup:has_cluster_system_dbs(Dbs) of
                                false ->
                                    chttpd:send_json(Req, 200, {[{state, cluster_enabled}]});
                                true ->
                                    chttpd:send_json(Req, 200, {[{state, cluster_finished}]})
                            end
                    end
            end
    end;
handle_setup_req(#httpd{} = Req) ->
    chttpd:send_method_not_allowed(Req, "GET,POST").

get_options(Options, Setup) ->
    ExtractValues = fun({Tag, Option}, OptionsAcc) ->
        case couch_util:get_value(Option, Setup) of
            undefined -> OptionsAcc;
            Value -> [{Tag, Value} | OptionsAcc]
        end
    end,
    lists:foldl(ExtractValues, [], Options).

handle_action("enable_cluster", Setup) ->
    Options = get_options(
        [
            {username, <<"username">>},
            {password, <<"password">>},
            {password_hash, <<"password_hash">>},
            {bind_address, <<"bind_address">>},
            {port, <<"port">>},
            {remote_node, <<"remote_node">>},
            {remote_current_user, <<"remote_current_user">>},
            {remote_current_password, <<"remote_current_password">>},
            {node_count, <<"node_count">>}
        ],
        Setup
    ),
    case setup:enable_cluster(Options) of
        {error, cluster_enabled} ->
            {error, <<"Cluster is already enabled">>};
        _ ->
            ok
    end;
handle_action("finish_cluster", Setup) ->
    couch_log:notice("finish_cluster: ~p~n", [couch_util:remove_sensitive_data(Setup)]),

    Options = get_options(
        [
            {ensure_dbs_exist, <<"ensure_dbs_exist">>}
        ],
        Setup
    ),
    case setup:finish_cluster(Options) of
        {error, cluster_finished} ->
            {error, <<"Cluster is already finished">>};
        Else ->
            couch_log:notice("finish_cluster: ~p~n", [Else]),
            ok
    end;
handle_action("enable_single_node", Setup) ->
    couch_log:notice("enable_single_node: ~p~n", [couch_util:remove_sensitive_data(Setup)]),

    Options = get_options(
        [
            {ensure_dbs_exist, <<"ensure_dbs_exist">>},
            {username, <<"username">>},
            {password, <<"password">>},
            {password_hash, <<"password_hash">>},
            {bind_address, <<"bind_address">>},
            {port, <<"port">>}
        ],
        Setup
    ),
    case setup:enable_single_node(Options) of
        {error, cluster_finished} ->
            {error, <<"Cluster is already finished">>};
        Else ->
            couch_log:notice("Else: ~p~n", [Else]),
            ok
    end;
handle_action("add_node", Setup) ->
    couch_log:notice("add_node: ~p~n", [couch_util:remove_sensitive_data(Setup)]),

    Options = get_options(
        [
            {username, <<"username">>},
            {password, <<"password">>},
            {host, <<"host">>},
            {port, <<"port">>},
            {name, <<"name">>}
        ],
        Setup
    ),
    case setup:add_node(Options) of
        {error, cluster_not_enabled} ->
            {error, <<"Cluster is not enabled.">>};
        {error, {conn_failed, {error, econnrefused}}} ->
            {error, <<"Add node failed. Invalid Host and/or Port.">>};
        {error, wrong_credentials} ->
            {error, <<"Add node failed. Invalid admin credentials,">>};
        {error, Message} ->
            {error, Message};
        _ ->
            ok
    end;
handle_action("remove_node", Setup) ->
    couch_log:notice("remove_node: ~p~n", [couch_util:remove_sensitive_data(Setup)]);
handle_action("receive_cookie", Setup) ->
    couch_log:notice("receive_cookie: ~p~n", [couch_util:remove_sensitive_data(Setup)]),
    Options = get_options(
        [
            {cookie, <<"cookie">>}
        ],
        Setup
    ),
    case setup:receive_cookie(Options) of
        {error, Error} ->
            {error, Error};
        _ ->
            ok
    end;
handle_action(_, _) ->
    couch_log:notice("invalid_action: ~n", []),
    {error, <<"Invalid Action'">>}.

get_body(Req) ->
    case catch couch_httpd:json_body_obj(Req) of
        {Body} ->
            Body;
        Else ->
            couch_log:notice("Body Fail: ~p~n", [Else]),
            couch_httpd:send_error(Req, 400, <<"bad_request">>, <<"Missing JSON body'">>)
    end.
