/*
 * Symphony - A modern community (forum/SNS/blog) platform written in Java.
 * Copyright (C) 2012-2017,  b3log.org & hacpai.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.b3log.symphony.service;

import org.b3log.latke.Keys;
import org.b3log.latke.ioc.inject.Inject;
import org.b3log.latke.logging.Level;
import org.b3log.latke.logging.Logger;
import org.b3log.latke.model.Pagination;
import org.b3log.latke.model.User;
import org.b3log.latke.repository.*;
import org.b3log.latke.service.LangPropsService;
import org.b3log.latke.service.ServiceException;
import org.b3log.latke.service.annotation.Service;
import org.b3log.latke.util.CollectionUtils;
import org.b3log.symphony.model.*;
import org.b3log.symphony.repository.*;
import org.b3log.symphony.util.Emotions;
import org.b3log.symphony.util.Symphonys;
import org.json.JSONArray;
import org.json.JSONObject;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * Pointtransfer query service.
 *
 * @author <a href="http://88250.b3log.org">Liang Ding</a>
 * @version 1.21.2.1, May 6, 2017
 * @since 1.3.0
 */
@Service
public class PointtransferQueryService {

    /**
     * Logger.
     */
    private static final Logger LOGGER = Logger.getLogger(PointtransferQueryService.class);

    /**
     * Pointtransfer repository.
     */
    @Inject
    private PointtransferRepository pointtransferRepository;

    /**
     * Article repository.
     */
    @Inject
    private ArticleRepository articleRepository;

    /**
     * User repository.
     */
    @Inject
    private UserRepository userRepository;

    /**
     * Comment repository.
     */
    @Inject
    private CommentRepository commentRepository;

    /**
     * Reward repository.
     */
    @Inject
    private RewardRepository rewardRepository;

    /**
     * Language service.
     */
    @Inject
    private LangPropsService langPropsService;

    /**
     * Avatar query service.
     */
    @Inject
    private AvatarQueryService avatarQueryService;

    /**
     * Gets the latest pointtransfers with the specified user id, type and fetch size.
     *
     * @param userId    the specified user id
     * @param type      the specified type
     * @param fetchSize the specified fetch size
     * @return pointtransfers, returns an empty list if not found
     */
    public List<JSONObject> getLatestPointtransfers(final String userId, final int type, final int fetchSize) {
        final List<JSONObject> ret = new ArrayList<>();

        final List<Filter> userFilters = new ArrayList<>();
        userFilters.add(new PropertyFilter(Pointtransfer.FROM_ID, FilterOperator.EQUAL, userId));
        userFilters.add(new PropertyFilter(Pointtransfer.TO_ID, FilterOperator.EQUAL, userId));

        final List<Filter> filters = new ArrayList<>();
        filters.add(new CompositeFilter(CompositeFilterOperator.OR, userFilters));
        filters.add(new PropertyFilter(Pointtransfer.TYPE, FilterOperator.EQUAL, type));

        final Query query = new Query().addSort(Keys.OBJECT_ID, SortDirection.DESCENDING).setCurrentPageNum(1)
                .setPageSize(fetchSize).setFilter(new CompositeFilter(CompositeFilterOperator.AND, filters));

        try {
            final JSONObject result = pointtransferRepository.get(query);

            return CollectionUtils.jsonArrayToList(result.optJSONArray(Keys.RESULTS));
        } catch (final RepositoryException e) {
            LOGGER.log(Level.ERROR, "Gets latest pointtransfers error", e);
        }

        return ret;
    }

    /**
     * Gets the top balance users with the specified fetch size.
     *
     * @param avatarViewMode the specified avatar view mode
     * @param fetchSize      the specified fetch size
     * @return users, returns an empty list if not found
     */
    public List<JSONObject> getTopBalanceUsers(final int avatarViewMode, final int fetchSize) {
        final List<JSONObject> ret = new ArrayList<>();

        final Query query = new Query().addSort(UserExt.USER_POINT, SortDirection.DESCENDING).setCurrentPageNum(1)
                .setPageSize(fetchSize).
                        setFilter(new PropertyFilter(UserExt.USER_JOIN_POINT_RANK,
                                FilterOperator.EQUAL, UserExt.USER_JOIN_POINT_RANK_C_JOIN));

        final int moneyUnit = Symphonys.getInt("pointExchangeUnit");
        try {
            final JSONObject result = userRepository.get(query);
            final List<JSONObject> users = CollectionUtils.jsonArrayToList(result.optJSONArray(Keys.RESULTS));

            for (final JSONObject user : users) {
                if (UserExt.USER_APP_ROLE_C_HACKER == user.optInt(UserExt.USER_APP_ROLE)) {
                    user.put(UserExt.USER_T_POINT_HEX, Integer.toHexString(user.optInt(UserExt.USER_POINT)));
                } else {
                    user.put(UserExt.USER_T_POINT_CC, UserExt.toCCString(user.optInt(UserExt.USER_POINT)));
                }

                user.put(Common.MONEY, (int) Math.floor(user.optInt(UserExt.USER_POINT) / moneyUnit));

                avatarQueryService.fillUserAvatarURL(avatarViewMode, user);

                ret.add(user);
            }
        } catch (final RepositoryException e) {
            LOGGER.log(Level.ERROR, "Gets top balance users error", e);
        }

        return ret;
    }

    /**
     * Gets the top consumption users with the specified fetch size.
     *
     * @param avatarViewMode the specified avatar view mode
     * @param fetchSize      the specified fetch size
     * @return users, returns an empty list if not found
     */
    public List<JSONObject> getTopConsumptionUsers(final int avatarViewMode, final int fetchSize) {
        final List<JSONObject> ret = new ArrayList<>();

        final Query query = new Query().addSort(UserExt.USER_USED_POINT, SortDirection.DESCENDING).setCurrentPageNum(1)
                .setPageSize(fetchSize).
                        setFilter(new PropertyFilter(UserExt.USER_JOIN_USED_POINT_RANK,
                                FilterOperator.EQUAL, UserExt.USER_JOIN_USED_POINT_RANK_C_JOIN));

        final int moneyUnit = Symphonys.getInt("pointExchangeUnit");
        try {
            final JSONObject result = userRepository.get(query);
            final List<JSONObject> users = CollectionUtils.jsonArrayToList(result.optJSONArray(Keys.RESULTS));

            for (final JSONObject user : users) {
                if (UserExt.USER_APP_ROLE_C_HACKER == user.optInt(UserExt.USER_APP_ROLE)) {
                    user.put(UserExt.USER_T_POINT_HEX, Integer.toHexString(user.optInt(UserExt.USER_POINT)));
                } else {
                    user.put(UserExt.USER_T_POINT_CC, UserExt.toCCString(user.optInt(UserExt.USER_POINT)));
                }

                user.put(Common.MONEY, (int) Math.floor(user.optInt(UserExt.USER_USED_POINT) / moneyUnit));

                avatarQueryService.fillUserAvatarURL(avatarViewMode, user);

                ret.add(user);
            }
        } catch (final RepositoryException e) {
            LOGGER.log(Level.ERROR, "Gets top consumption users error", e);
        }

        return ret;
    }

    /**
     * Gets the user points with the specified user id, page number and page size.
     *
     * @param userId         the specified user id
     * @param currentPageNum the specified page number
     * @param pageSize       the specified page size
     * @return result json object, for example,      <pre>
     * {
     *     "paginationRecordCount": int,
     *     "rslts": java.util.List[{
     *         Pointtransfer
     *     }, ....]
     * }
     * </pre>
     * @throws ServiceException service exception
     */
    public JSONObject getUserPoints(final String userId, final int currentPageNum, final int pageSize) throws ServiceException {
        final Query query = new Query().addSort(Keys.OBJECT_ID, SortDirection.DESCENDING)
                .setCurrentPageNum(currentPageNum).setPageSize(pageSize);
        final List<Filter> filters = new ArrayList<>();
        filters.add(new PropertyFilter(Pointtransfer.FROM_ID, FilterOperator.EQUAL, userId));
        filters.add(new PropertyFilter(Pointtransfer.TO_ID, FilterOperator.EQUAL, userId));
        query.setFilter(new CompositeFilter(CompositeFilterOperator.OR, filters));

        try {
            final JSONObject ret = pointtransferRepository.get(query);
            final JSONArray records = ret.optJSONArray(Keys.RESULTS);
            for (int i = 0; i < records.length(); i++) {
                final JSONObject record = records.optJSONObject(i);

                record.put(Common.CREATE_TIME, new Date(record.optLong(Pointtransfer.TIME)));

                final String toId = record.optString(Pointtransfer.TO_ID);
                final String fromId = record.optString(Pointtransfer.FROM_ID);

                String typeStr = record.optString(Pointtransfer.TYPE);
                if (("3".equals(typeStr) && userId.equals(toId))
                        || ("5".equals(typeStr) && userId.equals(fromId))
                        || ("9".equals(typeStr) && userId.equals(toId))
                        || ("14".equals(typeStr) && userId.equals(toId))
                        || ("22".equals(typeStr) && userId.equals(toId))) {
                    typeStr += "In";
                }

                if (fromId.equals(userId)) {
                    record.put(Common.BALANCE, record.optInt(Pointtransfer.FROM_BALANCE));
                    record.put(Common.OPERATION, "-");
                } else {
                    record.put(Common.BALANCE, record.optInt(Pointtransfer.TO_BALANCE));
                    record.put(Common.OPERATION, "+");
                }

                record.put(Common.DISPLAY_TYPE, langPropsService.get("pointType" + typeStr + "Label"));

                final int type = record.optInt(Pointtransfer.TYPE);
                final String dataId = record.optString(Pointtransfer.DATA_ID);
                String desTemplate = langPropsService.get("pointType" + typeStr + "DesLabel");

                switch (type) {
                    case Pointtransfer.TRANSFER_TYPE_C_DATA_EXPORT:
                        desTemplate = desTemplate.replace("{num}", record.optString(Pointtransfer.DATA_ID));

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_INIT:
                        desTemplate = desTemplate.replace("{point}", record.optString(Pointtransfer.SUM));

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_ADD_ARTICLE:
                        final JSONObject addArticle = articleRepository.get(dataId);
                        if (null == addArticle) {
                            desTemplate = langPropsService.get("removedLabel");

                            break;
                        }

                        final String addArticleLink = "<a href=\""
                                + addArticle.optString(Article.ARTICLE_PERMALINK) + "\">"
                                + addArticle.optString(Article.ARTICLE_TITLE) + "</a>";
                        desTemplate = desTemplate.replace("{article}", addArticleLink);

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_UPDATE_ARTICLE:
                        final JSONObject updateArticle = articleRepository.get(dataId);
                        if (null == updateArticle) {
                            desTemplate = langPropsService.get("removedLabel");

                            break;
                        }

                        final String updateArticleLink = "<a href=\""
                                + updateArticle.optString(Article.ARTICLE_PERMALINK) + "\">"
                                + updateArticle.optString(Article.ARTICLE_TITLE) + "</a>";
                        desTemplate = desTemplate.replace("{article}", updateArticleLink);

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_ADD_COMMENT:
                        final JSONObject comment = commentRepository.get(dataId);

                        if (null == comment) {
                            desTemplate = langPropsService.get("removedLabel");

                            break;
                        }

                        final String articleId = comment.optString(Comment.COMMENT_ON_ARTICLE_ID);
                        final JSONObject commentArticle = articleRepository.get(articleId);

                        final String commentArticleLink = "<a href=\""
                                + commentArticle.optString(Article.ARTICLE_PERMALINK) + "\">"
                                + commentArticle.optString(Article.ARTICLE_TITLE) + "</a>";
                        desTemplate = desTemplate.replace("{article}", commentArticleLink);

                        if ("3In".equals(typeStr)) {
                            final JSONObject commenter = userRepository.get(fromId);
                            final String commenterLink = "<a href=\"/member/" + commenter.optString(User.USER_NAME) + "\">"
                                    + commenter.optString(User.USER_NAME) + "</a>";

                            desTemplate = desTemplate.replace("{user}", commenterLink);
                        }

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_UPDATE_COMMENT:
                        final JSONObject comment32 = commentRepository.get(dataId);

                        if (null == comment32) {
                            desTemplate = langPropsService.get("removedLabel");

                            break;
                        }

                        final String articleId32 = comment32.optString(Comment.COMMENT_ON_ARTICLE_ID);
                        final JSONObject commentArticle32 = articleRepository.get(articleId32);

                        final String commentArticleLink32 = "<a href=\""
                                + commentArticle32.optString(Article.ARTICLE_PERMALINK) + "\">"
                                + commentArticle32.optString(Article.ARTICLE_TITLE) + "</a>";
                        desTemplate = desTemplate.replace("{article}", commentArticleLink32);

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_ADD_ARTICLE_REWARD:
                        final JSONObject addArticleReword = articleRepository.get(dataId);
                        if (null == addArticleReword) {
                            desTemplate = langPropsService.get("removedLabel");

                            break;
                        }

                        final String addArticleRewordLink = "<a href=\""
                                + addArticleReword.optString(Article.ARTICLE_PERMALINK) + "\">"
                                + addArticleReword.optString(Article.ARTICLE_TITLE) + "</a>";
                        desTemplate = desTemplate.replace("{article}", addArticleRewordLink);

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_ARTICLE_REWARD:
                        final JSONObject reward = rewardRepository.get(dataId);
                        String senderId = reward.optString(Reward.SENDER_ID);
                        if ("5In".equals(typeStr)) {
                            senderId = toId;
                        }
                        final String rewardArticleId = reward.optString(Reward.DATA_ID);

                        final JSONObject sender = userRepository.get(senderId);
                        final String senderLink = "<a href=\"/member/" + sender.optString(User.USER_NAME) + "\">"
                                + sender.optString(User.USER_NAME) + "</a>";
                        desTemplate = desTemplate.replace("{user}", senderLink);

                        final JSONObject articleReward = articleRepository.get(rewardArticleId);
                        if (null == articleReward) {
                            desTemplate = langPropsService.get("removedLabel");

                            break;
                        }

                        final String articleRewardLink = "<a href=\""
                                + articleReward.optString(Article.ARTICLE_PERMALINK) + "\">"
                                + articleReward.optString(Article.ARTICLE_TITLE) + "</a>";
                        desTemplate = desTemplate.replace("{article}", articleRewardLink);

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_COMMENT_REWARD:
                        final JSONObject reward14 = rewardRepository.get(dataId);
                        JSONObject user14;
                        if ("14In".equals(typeStr)) {
                            user14 = userRepository.get(fromId);
                        } else {
                            user14 = userRepository.get(toId);
                        }
                        final String commentId14 = reward14.optString(Reward.DATA_ID);
                        final JSONObject comment14 = commentRepository.get(commentId14);
                        if (null == comment14) {
                            desTemplate = langPropsService.get("removedLabel");

                            break;
                        }

                        final String articleId14 = comment14.optString(Comment.COMMENT_ON_ARTICLE_ID);

                        final String userLink14 = "<a href=\"/member/" + user14.optString(User.USER_NAME) + "\">"
                                + user14.optString(User.USER_NAME) + "</a>";
                        desTemplate = desTemplate.replace("{user}", userLink14);

                        final JSONObject article14 = articleRepository.get(articleId14);
                        final String articleLink = "<a href=\""
                                + article14.optString(Article.ARTICLE_PERMALINK) + "\">"
                                + article14.optString(Article.ARTICLE_TITLE) + "</a>";
                        desTemplate = desTemplate.replace("{article}", articleLink);

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_ARTICLE_THANK:
                        final JSONObject thank22 = rewardRepository.get(dataId);
                        JSONObject user22;
                        if ("22In".equals(typeStr)) {
                            user22 = userRepository.get(fromId);
                        } else {
                            user22 = userRepository.get(toId);
                        }
                        final String articleId22 = thank22.optString(Reward.DATA_ID);
                        final JSONObject article22 = articleRepository.get(articleId22);
                        if (null == article22) {
                            desTemplate = langPropsService.get("removedLabel");

                            break;
                        }

                        final String userLink22 = "<a href=\"/member/" + user22.optString(User.USER_NAME) + "\">"
                                + user22.optString(User.USER_NAME) + "</a>";
                        desTemplate = desTemplate.replace("{user}", userLink22);

                        final String articleLink22 = "<a href=\""
                                + article22.optString(Article.ARTICLE_PERMALINK) + "\">"
                                + article22.optString(Article.ARTICLE_TITLE) + "</a>";
                        desTemplate = desTemplate.replace("{article}", articleLink22);

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_INVITE_REGISTER:
                        final JSONObject newUser = userRepository.get(dataId);
                        final String newUserLink = "<a href=\"/member/" + newUser.optString(User.USER_NAME) + "\">"
                                + newUser.optString(User.USER_NAME) + "</a>";
                        desTemplate = desTemplate.replace("{user}", newUserLink);

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_INVITED_REGISTER:
                        final JSONObject referralUser = userRepository.get(dataId);
                        final String referralUserLink = "<a href=\"/member/" + referralUser.optString(User.USER_NAME) + "\">"
                                + referralUser.optString(User.USER_NAME) + "</a>";
                        desTemplate = desTemplate.replace("{user}", referralUserLink);

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_INVITECODE_USED:
                        final JSONObject newUser1 = userRepository.get(dataId);
                        final String newUserLink1 = "<a href=\"/member/" + newUser1.optString(User.USER_NAME) + "\">"
                                + newUser1.optString(User.USER_NAME) + "</a>";
                        desTemplate = desTemplate.replace("{user}", newUserLink1);

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_ACTIVITY_CHECKIN:
                    case Pointtransfer.TRANSFER_TYPE_C_ACTIVITY_YESTERDAY_LIVENESS_REWARD:
                    case Pointtransfer.TRANSFER_TYPE_C_ACTIVITY_1A0001:
                    case Pointtransfer.TRANSFER_TYPE_C_ACTIVITY_1A0001_COLLECT:
                    case Pointtransfer.TRANSFER_TYPE_C_ACTIVITY_CHARACTER:
                    case Pointtransfer.TRANSFER_TYPE_C_BUY_INVITECODE:
                    case Pointtransfer.TRANSFER_TYPE_C_ACTIVITY_EATINGSNAKE:
                    case Pointtransfer.TRANSFER_TYPE_C_ACTIVITY_EATINGSNAKE_COLLECT:
                    case Pointtransfer.TRANSFER_TYPE_C_ACTIVITY_GOBANG:
                    case Pointtransfer.TRANSFER_TYPE_C_ACTIVITY_GOBANG_COLLECT:
                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_AT_PARTICIPANTS:
                        final JSONObject comment20 = commentRepository.get(dataId);
                        if (null == comment20) {
                            desTemplate = langPropsService.get("removedLabel");

                            break;
                        }

                        final String articleId20 = comment20.optString(Comment.COMMENT_ON_ARTICLE_ID);
                        final JSONObject atParticipantsArticle = articleRepository.get(articleId20);
                        if (null == atParticipantsArticle) {
                            desTemplate = langPropsService.get("removedLabel");

                            break;
                        }

                        final String ArticleLink20 = "<a href=\""
                                + atParticipantsArticle.optString(Article.ARTICLE_PERMALINK) + "\">"
                                + atParticipantsArticle.optString(Article.ARTICLE_TITLE) + "</a>";
                        desTemplate = desTemplate.replace("{article}", ArticleLink20);

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_STICK_ARTICLE:
                        final JSONObject stickArticle = articleRepository.get(dataId);
                        if (null == stickArticle) {
                            desTemplate = langPropsService.get("removedLabel");

                            break;
                        }

                        final String stickArticleLink = "<a href=\""
                                + stickArticle.optString(Article.ARTICLE_PERMALINK) + "\">"
                                + stickArticle.optString(Article.ARTICLE_TITLE) + "</a>";
                        desTemplate = desTemplate.replace("{article}", stickArticleLink);

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_ACCOUNT2ACCOUNT:
                        JSONObject user9;
                        if ("9In".equals(typeStr)) {
                            user9 = userRepository.get(fromId);
                        } else {
                            user9 = userRepository.get(toId);
                        }

                        final String userLink = "<a href=\"/member/" + user9.optString(User.USER_NAME) + "\">"
                                + user9.optString(User.USER_NAME) + "</a>";
                        desTemplate = desTemplate.replace("{user}", userLink);

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_ACTIVITY_CHECKIN_STREAK:
                        desTemplate = desTemplate.replace("{point}",
                                String.valueOf(Pointtransfer.TRANSFER_SUM_C_ACTIVITY_CHECKINT_STREAK));
                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_CHARGE:
                        final String yuan = dataId.split("-")[0];
                        desTemplate = desTemplate.replace("{yuan}", yuan);

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_EXCHANGE:
                        final String exYuan = dataId;
                        desTemplate = desTemplate.replace("{yuan}", exYuan);

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_ABUSE_DEDUCT:
                        desTemplate = desTemplate.replace("{action}", dataId);
                        desTemplate = desTemplate.replace("{point}", record.optString(Pointtransfer.SUM));

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_ADD_ARTICLE_BROADCAST:
                        final JSONObject addArticleBroadcast = articleRepository.get(dataId);
                        if (null == addArticleBroadcast) {
                            desTemplate = langPropsService.get("removedLabel");

                            break;
                        }

                        final String addArticleBroadcastLink = "<a href=\""
                                + addArticleBroadcast.optString(Article.ARTICLE_PERMALINK) + "\">"
                                + addArticleBroadcast.optString(Article.ARTICLE_TITLE) + "</a>";
                        desTemplate = desTemplate.replace("{article}", addArticleBroadcastLink);

                        break;
                    case Pointtransfer.TRANSFER_TYPE_C_PERFECT_ARTICLE:
                        final JSONObject perfectArticle = articleRepository.get(dataId);
                        if (null == perfectArticle) {
                            desTemplate = langPropsService.get("removedLabel");

                            break;
                        }

                        final String perfectArticleLink = "<a href=\""
                                + perfectArticle.optString(Article.ARTICLE_PERMALINK) + "\">"
                                + perfectArticle.optString(Article.ARTICLE_TITLE) + "</a>";
                        desTemplate = desTemplate.replace("{article}", perfectArticleLink);

                        break;
                    default:
                        LOGGER.warn("Invalid point type [" + type + "]");
                }

                desTemplate = Emotions.convert(desTemplate);

                record.put(Common.DESCRIPTION, desTemplate);
            }

            final int recordCnt = ret.optJSONObject(Pagination.PAGINATION).optInt(Pagination.PAGINATION_RECORD_COUNT);
            ret.remove(Pagination.PAGINATION);
            ret.put(Pagination.PAGINATION_RECORD_COUNT, recordCnt);

            return ret;
        } catch (final RepositoryException e) {
            LOGGER.log(Level.ERROR, "Gets user points failed", e);
            throw new ServiceException(e);
        }
    }
}
