package com.zyd.blog.business.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.zyd.blog.business.annotation.RedisCache;
import com.zyd.blog.business.dto.BizCommentDTO;
import com.zyd.blog.business.entity.Comment;
import com.zyd.blog.business.entity.User;
import com.zyd.blog.business.enums.CommentStatusEnum;
import com.zyd.blog.business.enums.ConfigKeyEnum;
import com.zyd.blog.business.enums.TemplateKeyEnum;
import com.zyd.blog.business.service.BizCommentService;
import com.zyd.blog.business.service.MailService;
import com.zyd.blog.business.service.SysConfigService;
import com.zyd.blog.business.vo.CommentConditionVO;
import com.zyd.blog.framework.exception.ZhydCommentException;
import com.zyd.blog.framework.holder.RequestHolder;
import com.zyd.blog.persistence.beans.BizComment;
import com.zyd.blog.persistence.beans.SysConfig;
import com.zyd.blog.persistence.mapper.BizCommentMapper;
import com.zyd.blog.util.*;
import eu.bitwalker.useragentutils.Browser;
import eu.bitwalker.useragentutils.OperatingSystem;
import eu.bitwalker.useragentutils.UserAgent;
import eu.bitwalker.useragentutils.Version;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 评论
 *
 * @author yadong.zhang (yadong.zhang0415(a)gmail.com)
 * @version 1.0
 * @website https://docs.zhyd.me
 * @date 2018/4/16 16:26
 * @since 1.0
 */
@Slf4j
@Service
public class BizCommentServiceImpl implements BizCommentService {

    @Autowired
    private BizCommentMapper bizCommentMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private MailService mailService;

    @Autowired
    private SysConfigService configService;

    /**
     * 分页查询
     *
     * @param vo
     * @return
     */
    @Override
    public PageInfo<Comment> findPageBreakByCondition(CommentConditionVO vo) {
        PageHelper.startPage(vo.getPageNumber(), vo.getPageSize());
        List<BizComment> list = bizCommentMapper.findPageBreakByCondition(vo);
        List<Comment> boList = this.getComments(list);
        if (boList == null) {
            return null;
        }
        PageInfo bean = new PageInfo<BizComment>(list);
        bean.setList(boList);
        return bean;
    }

    /**
     * @param vo
     * @return
     */
    @Override
    public Map<String, Object> list(CommentConditionVO vo) {
        PageInfo pageInfo = findPageBreakByCondition(vo);
        Map<String, Object> map = new HashMap<>();
        if (pageInfo != null) {
            map.put("commentList", convert2DTO(pageInfo.getList()));
            map.put("total", pageInfo.getTotal());
            map.put("hasNextPage", pageInfo.isHasNextPage());
            map.put("nextPage", pageInfo.getNextPage());
        }
        return map;
    }

    private List<BizCommentDTO> convert2DTO(List<Comment> list) {
        if (CollectionUtils.isEmpty(list)) {
            return null;
        }
        List<BizCommentDTO> dtoList = new LinkedList<>();
        for (Comment comment : list) {
            BizCommentDTO dto = BeanConvertUtil.doConvert(comment, BizCommentDTO.class);
            dto.setParentDTO(BeanConvertUtil.doConvert(comment.getParent(), BizCommentDTO.class));
            if (null != comment.getUser()) {
                dto.setUserType(comment.getUser().getUserTypeEnum());
            }
            dtoList.add(dto);
        }
        return dtoList;
    }

    /**
     * admin发表评论
     *
     * @param comment
     * @return
     */
    @Override
    @RedisCache(flush = true)
    public void commentForAdmin(Comment comment) throws ZhydCommentException {
        Map config = configService.getConfigs();
        User user = SessionUtil.getUser();
        comment.setQq(user.getQq());
        comment.setEmail(user.getEmail());
        comment.setNickname(user.getNickname());
        comment.setAvatar(user.getAvatar());
        comment.setUrl((String) config.get(ConfigKeyEnum.SITE_URL.getKey()));
        comment.setUserId(user.getId());
        comment.setStatus(CommentStatusEnum.APPROVED.toString());
        comment.setPid(comment.getId());
        comment.setId(null);
        this.comment(comment);
    }

    /**
     * 发表评论
     *
     * @param comment
     * @return
     */
    @Override
    @RedisCache(flush = true)
    public Comment comment(Comment comment) throws ZhydCommentException {
        SysConfig sysConfig = configService.getByKey(ConfigKeyEnum.ANONYMOUS.getKey());
        boolean anonymous = true;
        if (null != sysConfig) {
            anonymous = "1".equals(sysConfig.getConfigValue());
        }

        // 非匿名且未登录
        if (!anonymous && !SessionUtil.isLogin()) {
            throw new ZhydCommentException("站长已关闭匿名评论，请先登录！");
        }

        // 过滤文本内容，防止xss
        this.filterContent(comment);

        // 已登录且非匿名，使用当前登录用户的信息评论
        if (SessionUtil.isLogin()) {
            this.setCurrentLoginUserInfo(comment);
        } else {
            this.setCurrentAnonymousUserInfo(comment);
        }

        // 用户没有头像时， 使用随机默认的头像
        if (StringUtils.isEmpty(comment.getAvatar())) {
            List<String> avatars = configService.getRandomUserAvatar();
            if (!CollectionUtils.isEmpty(avatars)) {
                Collections.shuffle(avatars);
                int randomIndex = new Random().nextInt(avatars.size());
                comment.setAvatar(avatars.get(randomIndex));
            }
        }

        if (StringUtils.isEmpty(comment.getStatus())) {
            comment.setStatus(CommentStatusEnum.VERIFYING.toString());
        }

        // set当前评论者的设备信息
        this.setCurrentDeviceInfo(comment);

        // set当前评论者的位置信息
        this.setCurrentLocation(comment);

        // 保存
        this.insert(comment);

        // 发送邮件通知
        this.sendEmail(comment);
        return comment;
    }

    /**
     * 过滤评论内容
     *
     * @param comment
     */
    private void filterContent(Comment comment) {
        String content = comment.getContent();
        if (StringUtils.isEmpty(content) || "\n".equals(content)) {
            throw new ZhydCommentException("说点什么吧");
        }
        String url = comment.getUrl();
        String avatar = comment.getAvatar();
        if ((!StringUtils.isEmpty(avatar) && !RegexUtils.isUrl(avatar)) || (!StringUtils.isEmpty(url) && !RegexUtils.isUrl(url))) {
            throw new ZhydCommentException("链接地址不正确");
        }
        // 过滤非法属性和无用的空标签
        if (!XssKillerUtil.isValid(content) || !XssKillerUtil.isValid(comment.getAvatar())
                || !XssKillerUtil.isValid(comment.getUrl()) || !XssKillerUtil.isValid(comment.getNickname())
                || !XssKillerUtil.isValid(comment.getQq()) || !XssKillerUtil.isValid(comment.getEmail())) {
            throw new ZhydCommentException("请不要使用特殊标签");
        }
        content = XssKillerUtil.clean(content.trim()).replaceAll("(<p><br></p>)|(<p></p>)", "");
        if (StringUtils.isEmpty(content) || "\n".equals(content)) {
            throw new ZhydCommentException("说点什么吧");
        }
        comment.setContent(content);
    }

    /**
     * 保存当前匿名用户的信息
     *
     * @param comment
     */
    private void setCurrentAnonymousUserInfo(Comment comment) {
        if (StringUtils.isEmpty(comment.getNickname())) {
            throw new ZhydCommentException("必须输入昵称");
        }
        comment.setNickname(HtmlUtil.html2Text(comment.getNickname()));
        comment.setQq(HtmlUtil.html2Text(comment.getQq()));
        comment.setAvatar(HtmlUtil.html2Text(comment.getAvatar()));
        comment.setEmail(HtmlUtil.html2Text(comment.getEmail()));
        comment.setUrl(HtmlUtil.html2Text(comment.getUrl()));
    }

    /**
     * 保存当前登录用户的信息
     *
     * @param comment
     */
    private void setCurrentLoginUserInfo(Comment comment) {
        User loginUser = SessionUtil.getUser();
        comment.setNickname(HtmlUtil.html2Text(loginUser.getNickname()));
        comment.setQq(HtmlUtil.html2Text(loginUser.getQq()));
        comment.setAvatar(HtmlUtil.html2Text(loginUser.getAvatar()));
        comment.setEmail(HtmlUtil.html2Text(loginUser.getEmail()));
        comment.setUrl(HtmlUtil.html2Text(loginUser.getBlog()));
        comment.setUserId(loginUser.getId());
    }

    /**
     * 保存当前评论时的设备信息
     *
     * @param comment
     */
    private void setCurrentDeviceInfo(Comment comment) {
        String ua = RequestUtil.getUa();
        UserAgent agent = UserAgent.parseUserAgentString(ua);
        Browser browser = agent.getBrowser();
        String browserInfo = browser.getName();
        Version version = agent.getBrowserVersion();
        if (version != null) {
            browserInfo += " " + version.getVersion();
        }
        comment.setBrowser(browserInfo);
        OperatingSystem os = agent.getOperatingSystem();
        comment.setOs(os.getName());
        comment.setIp(RequestUtil.getIp());
    }

    /**
     * 保存当前评论时的位置信息
     *
     * @param comment
     */
    private void setCurrentLocation(Comment comment) {
        Map config = configService.getConfigs();
        try {
            String locationJson = RestClientUtil.get(UrlBuildUtil.getLocationByIp(comment.getIp(), (String) config.get(ConfigKeyEnum.BAIDU_API_AK.getKey())));
            JSONObject localtionContent = JSONObject.parseObject(locationJson).getJSONObject("content");
            if (localtionContent.containsKey("point")) {
                JSONObject point = localtionContent.getJSONObject("point");
                comment.setLat(point.getString("y"));
                comment.setLng(point.getString("x"));
            }

            if (localtionContent.containsKey("address_detail")) {
                JSONObject addressDetail = localtionContent.getJSONObject("address_detail");
                String city = addressDetail.getString("city");
                String district = addressDetail.getString("district");
                String street = addressDetail.getString("street");
                String address = addressDetail.getString("province") + (StringUtils.isEmpty(city) ? "" : city) +
                        (StringUtils.isEmpty(district) ? "" : district) +
                        (StringUtils.isEmpty(street) ? "" : street);
                comment.setAddress(address);
            }
        } catch (Exception e) {
            log.error("获取地址失败", e);
        }
        if (StringUtils.isEmpty(comment.getAddress())) {
            comment.setAddress("未知");
        }
    }


    private void sendEmail(Comment comment) {
        // 发送邮件通知，此处如发生异常不应阻塞当前的业务流程
        // 可以进行日志记录等操作
        try {
            if (null != comment.getPid() && 0 != comment.getPid()) {
                // 给被评论的用户发送通知
                Comment commentDB = this.getByPrimaryKey(comment.getPid());
                mailService.send(commentDB, TemplateKeyEnum.TM_COMMENT_REPLY, false);
            } else {
                mailService.sendToAdmin(comment);
            }
        } catch (Exception e) {
            log.error("发送评论通知邮件时发生异常", e);
        }
    }

    /**
     * 查询近期评论
     *
     * @param pageSize
     * @return
     */
    @Override
    @RedisCache
    public List<Comment> listRecentComment(int pageSize) {
        CommentConditionVO vo = new CommentConditionVO();
        vo.setPageSize(pageSize);
        vo.setStatus(CommentStatusEnum.APPROVED.toString());
        PageHelper.startPage(vo.getPageNumber(), vo.getPageSize());
        List<BizComment> list = bizCommentMapper.findPageBreakByCondition(vo);
        return getComments(list);
    }

    private List<Comment> getComments(List<BizComment> list) {
        if (CollectionUtils.isEmpty(list)) {
            return null;
        }
        List<Comment> boList = new ArrayList<>();
        for (BizComment bizComment : list) {
            boList.add(new Comment(bizComment));
        }
        return boList;
    }

    /**
     * 查询未审核的评论
     *
     * @param pageSize
     * @return
     */
    @Override
    public List<Comment> listVerifying(int pageSize) {
        CommentConditionVO vo = new CommentConditionVO();
        vo.setPageSize(pageSize);
        vo.setStatus(CommentStatusEnum.VERIFYING.toString());
        PageInfo pageInfo = findPageBreakByCondition(vo);
        return null == pageInfo ? null : pageInfo.getList();
    }

    /**
     * 点赞
     *
     * @param id
     */
    @Override
    @RedisCache(flush = true)
    public void doSupport(Long id) {
        String key = IpUtil.getRealIp(RequestHolder.getRequest()) + "_doSupport_" + id;
        ValueOperations<String, Object> operations = redisTemplate.opsForValue();
        if (redisTemplate.hasKey(key)) {
            throw new ZhydCommentException("一个小时只能点一次赞哈~");
        }
        bizCommentMapper.doSupport(id);
        operations.set(key, id, 1, TimeUnit.HOURS);
    }

    /**
     * 点踩
     *
     * @param id
     */
    @Override
    @RedisCache(flush = true)
    public void doOppose(Long id) {
        String key = IpUtil.getRealIp(RequestHolder.getRequest()) + "_doOppose_" + id;
        ValueOperations<String, Object> operations = redisTemplate.opsForValue();
        if (redisTemplate.hasKey(key)) {
            throw new ZhydCommentException("一个小时只能踩一次哈~又没什么深仇大恨");
        }
        bizCommentMapper.doOppose(id);
        operations.set(key, id, 1, TimeUnit.HOURS);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @RedisCache(flush = true)
    public Comment insert(Comment entity) {
        Assert.notNull(entity, "Comment不可为空！");
        entity.setUpdateTime(new Date());
        entity.setCreateTime(new Date());
        bizCommentMapper.insertSelective(entity.getBizComment());
        return entity;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @RedisCache(flush = true)
    public boolean removeByPrimaryKey(Long primaryKey) {
        return bizCommentMapper.deleteByPrimaryKey(primaryKey) > 0;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @RedisCache(flush = true)
    public boolean updateSelective(Comment entity) {
        Assert.notNull(entity, "Comment不可为空！");
        entity.setUpdateTime(new Date());
        return bizCommentMapper.updateByPrimaryKeySelective(entity.getBizComment()) > 0;
    }

    @Override
    public Comment getByPrimaryKey(Long primaryKey) {
        Assert.notNull(primaryKey, "PrimaryKey不可为空！");
        BizComment entity = bizCommentMapper.getById(primaryKey);
        return null == entity ? null : new Comment(entity);
    }
}
