#!/usr/bin/env escript
%% -*- erlang-indent-level: 4;indent-tabs-mode: nil;fill-column: 92 -*-
%% %% ex: ts=4 sw=4 et ft=erlang
%%! -hidden
%%
%% Copyright(c) 2014 Chef, Inc
%% All Rights Reserved

%% using compiled mode gives more informative error traces
-mode(compile).

%% @doc Migrate all orgs, one at a time, and output the summary results to stdout
%% in the form:
%%
%% `
%% Successfully Migrated: org-name-list
%%     Failed to Migrate: org-name-list
%%         Not Attempted: org-name-list
%% '
%%
%% Usage:
%%  migrate:
%%    will run with mover_phase_1_migration_callback and normal noise level
%%    with default ?MOVER_MOD migration processor
%%  migrate silent:
%%    will run with mover_phase_1_migration_callback and silent noise level
%%    with default ?MOVER_MOD migration processor
%%  migrate <migration_callback> <noise_level>:
%%    will run a migration on any valid <migration_callback> and noise level
%%    where <noise_level> is "normal" or "silent"
%%    with default ?MOVER_MOD migration processor
%%  migrate <migration_callback> <noise_level> <migration_processor>:
%%    will run a migration on any valid <migration_callback> and noise level
%%    where <noise_level> is "normal" or "silent", and <migration_processor>
%%    is a valid migration processor (e.g. 'mover_batch_migrator' or
%%    'mover_non_org_batch_migrator')
%%
%% <b>Important</b>: this must be run as root or connect will fail.
%% If an error occurs, it will also be displayed on the console.
%% If any org encounters errors in migrating or resetting state,
%% the script will exit with exit code 1. Otherwise it will exit
%% with exit code0, including if there are no orgs to migrate.



-define(SELF, 'migrate-script@127.0.0.1').
-define(MOVER, 'mover@127.0.0.1').
-define(MOVER_COOKIE, 'mover').
-define(MOVER_MOD, "mover_batch_migrator").
-define(SUCCESS_EXIT, 0).
-define(FAILURE_EXIT, 1).


main([]) ->
    main(mover_phase_1_migration_callback, normal, ?MOVER_MOD);
main(["silent"]) ->
    main(mover_phase_1_migration_callback, silent, ?MOVER_MOD);
main([Other]) ->
    bad_noise_argument(Other);
main([MigrationType, NoiseLevel]) ->
    main([MigrationType, NoiseLevel, ?MOVER_MOD]);
main([MigrationType, NoiseLevel, MigrationProcessor]) ->
    case NoiseLevel of
        "silent" ->
            main(list_to_atom(MigrationType), silent, list_to_atom(MigrationProcessor));
        "normal" ->
            main(list_to_atom(MigrationType), normal, list_to_atom(MigrationProcessor));
        _ ->
            bad_noise_argument(NoiseLevel)
    end.

main(MigrationType, NoiseLevel, MigrationProcessor) ->
    {ExitCode, Message} = try
        init_network(MigrationProcessor),
        Results = migrate(MigrationType, MigrationProcessor),
        % if successful_org is undefined in the results proplist
        % then do a simple parse and format (likely transient queue migration).
        case proplists:is_defined(successful_orgs, Results) of
            false ->
                simple_parse_and_format(Results);
            _ ->
                parse_and_format(NoiseLevel, Results)
        end
    catch
        error:{error, HaltWith} ->
            {?FAILURE_EXIT, HaltWith};
        E:Why ->
            error_logger:error_report({E, Why}),
            io:fwrite("Unknown error ~p ~n Unknown reason ~p ~n", [E, Why]),
            {?FAILURE_EXIT, "An unknown error has occurred."}
    end,
    io:fwrite("~n~s~n~n", [Message]),
    halt(ExitCode).

bad_noise_argument(Argument) ->
    io:fwrite("Unknown argument(s): ~p.~nUsage: migrate [silent] or migrate [migration_type] [silent|normal]~n", [Argument]),
    halt(?FAILURE_EXIT).

init_network(MigrationProcessor) ->
    net_kernel:start([?SELF]),
    erlang:set_cookie(?MOVER, ?MOVER_COOKIE),
    verify_ping(net_adm:ping(?MOVER), "Could not connect to mover service"),
    R = try
            rpc:call(?MOVER, MigrationProcessor, ping, [])
        catch
            _M:_R  ->
               pang
        end,
    verify_ping(R, "RPC to mover service failed").

migrate(MigrationType, MigrationProcessor) ->
% Orgs migrationc case:
%[{status,complete},
%       {successful_orgs,["org1"]},
%       {failed_orgs,["org2"]},
%       {reset_failed,["org3", "org4"]}].
% Transient queue migration case:
% [{status, complete}]
    rpc:call(?MOVER, MigrationProcessor, migrate_all, [MigrationType]).

simple_parse_and_format(Results) ->
    Exit = status_to_exit_code(proplists:get_value(status, Results)),
    {Exit, exit_code_message(Exit)}.

parse_and_format(NoiseLevel, Results) ->
    C0 = status_to_exit_code(proplists:get_value(status, Results)),
    {C1, Success} = list_to_exit_code(successful_orgs, Results, ?SUCCESS_EXIT),
    {C2, Failed} = list_to_exit_code(failed_orgs, Results, ?FAILURE_EXIT),
    {C3, ResetFail} = list_to_exit_code(reset_failed, Results, ?FAILURE_EXIT),
    write_org_list(NoiseLevel, "Successfully Migrated", Success),
    write_org_list(NoiseLevel, "    Failed to Migrate", Failed),
    write_org_list(NoiseLevel, "        Not Attempted", ResetFail),
    Exit = C0 bor C1 bor C2 bor C3,
    {Exit, exit_code_message(Exit)}.

status_to_exit_code(complete) -> ?SUCCESS_EXIT;
status_to_exit_code(aborted) -> ?FAILURE_EXIT.

exit_code_message(?FAILURE_EXIT) ->
    "Errors or warnings occurred.";
exit_code_message(?SUCCESS_EXIT) ->
    "Migrations completed successfully.".

list_to_exit_code(Key, Results, ResponseCode) ->
    List = proplists:get_value(Key, Results),
    Code = case length(List) of
        0 -> 0;
        _ -> ResponseCode
    end,
    {Code, List}.

write_org_list(silent, _, _) -> ok;
write_org_list(_, Subject, []) ->
    io:fwrite("  ~s: none~n", [Subject]);
write_org_list(_, Subject, Orgs) ->
    io:fwrite("  ~s: ~s~n", [Subject, string:join(Orgs, ", ")]).


verify_ping(pong, _HaltWith) -> ok;
verify_ping(_Other, HaltWith) ->
    % Doing a halt from here will not actual set exit code, presumably
    % because trhere's additional program flow following this call.
    % Throw an error instead.
    error({error, HaltWith}).



