%%%-------------------------------------------------------------------
%%% @author aresei
%%% @copyright (C) 2018, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 29. 九月 2018 17:01
%%%-------------------------------------------------------------------
-module(mysql_provider).
-author("aresei").

%% API
-export([get_row/2, get_row/3, get_all/2, get_all/3]).
-export([update/4, update_by/2, update_by/3, insert/4]).

%% 从数据库中查找一行记录
-spec get_row(ConnPid :: pid(), Sql::binary()) -> {ok, Record::map()} | undefined.
get_row(ConnPid, Sql) when is_pid(ConnPid), is_binary(Sql) ->
    lager:debug("[mysql_client] get_row sql is: ~p", [Sql]),
    case mysql:query(ConnPid, Sql) of
        {ok, Names, [Row | _]} ->
            {ok, maps:from_list(lists:zip(Names, Row))};
        {ok, _, []} ->
            undefined;
        Error ->
            lager:warning("[mysql_client] get error: ~p", [Error]),
            undefined
    end.

-spec get_row(ConnPid :: pid(), Sql::binary(), Params::list()) -> {ok, Record::map()} | undefined.
get_row(ConnPid, Sql, Params) when is_pid(ConnPid), is_binary(Sql), is_list(Params) ->
    lager:debug("[mysql_client] get_row sql is: ~p, params: ~p", [Sql, Params]),
    case mysql:query(ConnPid, Sql, Params) of
        {ok, Names, [Row | _]} ->
            {ok, maps:from_list(lists:zip(Names, Row))};
        {ok, _, []} ->
            undefined;
        Error ->
            lager:warning("[mysql_client] get error: ~p", [Error]),
            undefined
    end.

-spec get_all(ConnPid :: pid(), Sql::binary()) -> {ok, Rows::list()} | {error, Reason :: any()}.
get_all(ConnPid, Sql) when is_pid(ConnPid), is_binary(Sql) ->
    lager:debug("[mysql_client] get_all sql is: ~p", [Sql]),
    case mysql:query(ConnPid, Sql) of
        {ok, Names, Rows} ->
            {ok, lists:map(fun(Row) -> maps:from_list(lists:zip(Names, Row)) end, Rows)};
        {error, Reason} ->
            lager:warning("[mysql_client] get error: ~p", [Reason]),
            {error, Reason}
    end.

-spec get_all(ConnPid :: pid(), Sql::binary(), Params::list()) -> {ok, Rows::list()} | {error, Reason::any()}.
get_all(ConnPid, Sql, Params) when is_pid(ConnPid), is_binary(Sql), is_list(Params) ->
    lager:debug("[mysql_client] get_all sql is: ~p, params: ~p", [Sql, Params]),
    case mysql:query(ConnPid, Sql, Params) of
        {ok, Names, Rows} ->
            {ok, lists:map(fun(Row) -> maps:from_list(lists:zip(Names, Row)) end, Rows)};
        {error, Reason} ->
            lager:warning("[mysql_client] get error: ~p", [Reason]),
            {error, Reason}
    end.

-spec insert(ConnPid :: pid(), Table :: binary(), Fields :: map() | list(), boolean()) ->
    ok | {ok, InsertId :: integer()} | {error, Reason :: any()}.
insert(ConnPid, Table, Fields, FetchInsertId) when is_pid(ConnPid), is_binary(Table), is_map(Fields), is_boolean(FetchInsertId) ->
    insert(ConnPid, Table, maps:to_list(Fields), FetchInsertId);
insert(ConnPid, Table, Fields, FetchInsertId) when is_pid(ConnPid), is_binary(Table), is_list(Fields), is_boolean(FetchInsertId) ->
    {Keys, Values} = kvs(Fields),

    FieldSql = iolist_to_binary(lists:join(<<", ">>, Keys)),
    Placeholders = lists:duplicate(length(Keys), <<"?">>),
    ValuesPlaceholder = iolist_to_binary(lists:join(<<", ">>, Placeholders)),

    Sql = <<"INSERT INTO ", Table/binary, "(", FieldSql/binary, ") VALUES(", ValuesPlaceholder/binary, ")">>,
    lager:debug("[mysql_client] insert sql is: ~p, params: ~p", [Sql, Values]),
    case mysql:query(ConnPid, Sql, Values) of
        ok ->
            case FetchInsertId of
                true ->
                    InsertId = mysql:insert_id(ConnPid),
                    {ok, InsertId};
                false ->
                    ok
            end;
        Error ->
            Error
    end.

-spec update_by(ConnPid :: pid(), UpdateSql :: binary()) -> {ok, AffectedRows :: integer()} | {error, Reason :: any()}.
update_by(ConnPid, UpdateSql) when is_pid(ConnPid), is_binary(UpdateSql) ->
    lager:debug("[mysql_client] updateBySql sql: ~p", [UpdateSql]),
    case mysql:query(ConnPid, UpdateSql) of
        ok ->
            AffectedRows = mysql:affected_rows(ConnPid),
            {ok, AffectedRows};
        Error ->
            Error
    end.

-spec update_by(ConnPid :: pid(), UpdateSql :: binary(), Params :: list()) -> {ok, AffectedRows :: integer()} | {error, Reason :: any()}.
update_by(ConnPid, UpdateSql, Params) when is_pid(ConnPid), is_binary(UpdateSql) ->
    lager:debug("[mysql_client] updateBySql sql: ~p, params: ~p", [UpdateSql, Params]),
    case mysql:query(ConnPid, UpdateSql, Params) of
        ok ->
            AffectedRows = mysql:affected_rows(ConnPid),
            {ok, AffectedRows};
        Error ->
            Error
    end.

-spec update(ConnPid :: pid(), Sql :: binary(), Fields :: map(), WhereFields :: map()) ->
    {ok, AffectedRows::integer()} | {error, Reason::any()}.
update(ConnPid, Table, Fields, WhereFields) when is_pid(ConnPid), is_binary(Table), is_map(Fields), is_map(WhereFields) ->
    %% 拼接set
    {SetKeys, SetVals} = kvs(Fields),
    SetKeys1 = lists:map(fun(K) when is_binary(K) -> <<"`", K/binary, "` = ?">> end, SetKeys),
    SetSql = iolist_to_binary(lists:join(<<", ">>, SetKeys1)),

    %% 拼接where
    {WhereKeys, WhereVals} = kvs(WhereFields),
    WhereKeys1 = lists:map(fun(K) when is_binary(K) -> <<"`", K/binary, "` = ?">> end, WhereKeys),
    WhereSql = iolist_to_binary(lists:join(<<" AND ">>, WhereKeys1)),

    Params = SetVals ++ WhereVals,

    Sql = <<"UPDATE ", Table/binary, " SET ",  SetSql/binary, " WHERE ", WhereSql/binary>>,
    lager:debug("[mysql_client] update sql is: ~p, params: ~p", [Sql, Params]),
    case mysql:query(ConnPid, Sql, Params) of
        ok ->
            AffectedRows = mysql:affected_rows(ConnPid),
            {ok, AffectedRows};
        Error ->
            lager:error("[mysql_client] update sql: ~p, params: ~p, get a error: ~p", [Sql, Params, Error]),
            Error
    end.

-spec kvs(Fields :: map() | list()) -> {Keys :: list(), Values :: list()}.
kvs(Fields) when is_map(Fields) ->
    kvs(maps:to_list(Fields));
kvs(Fields) when is_list(Fields) ->
    {Keys0, Values0} = lists:foldl(fun({K, V}, {Acc0, Acc1}) -> {[K|Acc0], [V|Acc1]} end, {[], []}, Fields),
    {lists:reverse(Keys0), lists:reverse(Values0)}.