package cn.quantgroup.xyqb.service.wechat.impl;

import cn.quantgroup.xyqb.Constants;
import cn.quantgroup.xyqb.entity.WechatUserInfo;
import cn.quantgroup.xyqb.event.WechatBindEvent;
import cn.quantgroup.xyqb.exception.WechatRelateUserException;
import cn.quantgroup.xyqb.model.webchat.AccessTokenResponse;
import cn.quantgroup.xyqb.model.webchat.WechatEventMsg;
import cn.quantgroup.xyqb.repository.IWeChatUserRepository;
import cn.quantgroup.xyqb.service.http.IHttpService;
import cn.quantgroup.xyqb.service.wechat.IWechatService;
import cn.quantgroup.xyqb.util.ValidationUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * Created by Miraculous on 2017/1/19.
 */
@Slf4j
@Service
public class WechatServiceImpl implements IWechatService {
    private static final String WECHAT_TOKEN_KEY_PREFIX = "wechat:token:";
    @Value("${wechat.appid}")
    private String appId;
    @Value("${wechat.secret}")
    private String secret;
    private String accessTokenUrl;
    private String refreshTokenUrl = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s";
    @Resource
    private IHttpService httpService;
    @Resource
    private IWeChatUserRepository weChatUserRepository;
    @Autowired
    @Qualifier("stringRedisTemplate")
    private RedisTemplate<String, String> redisTemplate;

    @Resource
    private ApplicationEventPublisher applicationEventPublisher;

    @PostConstruct
    private void init() {
        accessTokenUrl = String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&grant_type=authorization_code&code=", appId, secret) + "%s";

        refreshTokenUrl = String.format("https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=", appId) + "%s";
    }

    @Override
    public AccessTokenResponse getToken(String code) {
        if (StringUtils.isEmpty(code)) {
            return null;
        }
        String key = WECHAT_TOKEN_KEY_PREFIX + code;

        String resultStr = redisTemplate.opsForValue().get(key);
        if (StringUtils.isEmpty(resultStr)) {
            String response = getTokenFromWechatServer(code);
            if (StringUtils.isEmpty(response)) {
                return null;
            }
            try {
                AccessTokenResponse accessTokenResponse = JSONObject.parseObject(response,
                        AccessTokenResponse.class);
                if (accessTokenResponse == null) {
                    return null;
                }
                accessTokenResponse.setInitialTime(System.currentTimeMillis() - Constants.MILLIS_OF_TEN_SECOND);
                redisTemplate.opsForValue().set(key, JSONObject.toJSONString(accessTokenResponse), accessTokenResponse.getExpiresIn() + Constants.THOUSAND_SECOND, TimeUnit.SECONDS);
                return accessTokenResponse;
            } catch (Exception ex) {
                return null;
            }
        } else {
            try {
                AccessTokenResponse response = JSONObject.parseObject(resultStr, AccessTokenResponse.class);
                // 刷新
                if (response.getInitialTime() + response.getExpiresIn() * Constants.MILLIS_PER_SECOND > System.currentTimeMillis()) {
                    String refreshTokenStr = refreshToken(response.getRefreshToken());
                    response = JSONObject.parseObject(refreshTokenStr, AccessTokenResponse.class);
                    if (response == null) {
                        return null;
                    }
                    response.setInitialTime(System.currentTimeMillis() - Constants.MILLIS_OF_TEN_SECOND);
                }
                redisTemplate.opsForValue().set(key, JSONObject.toJSONString(response), response.getExpiresIn() + Constants.THOUSAND_SECOND, TimeUnit.SECONDS);
                return response;
            } catch (Exception ex) {
                return null;
            }
        }
    }

    @Override
    public WechatUserInfo getWechatUserInfoFromWechatServer(String token, String openId) {
        if (StringUtils.isEmpty(token) || StringUtils.isEmpty(openId)) {
            return null;
        }
        String accessUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN";
        String url = String.format(accessUserInfoUrl, token, openId);
        String result = httpService.get(url);
        return JSONObject.parseObject(result, WechatUserInfo.class);
    }

    @Override
    public WechatUserInfo findWechatUserInfoFromDb(String openId) {
        return weChatUserRepository.findByOpenIdAndAppName(openId, "xyqb");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public WechatUserInfo saveWechatUserInfo(WechatUserInfo userInfo) {
        log.info("微信信息保存开始：{}", JSON.toJSONString(userInfo));
        if (Objects.isNull(userInfo) || Objects.isNull(userInfo.getOpenId())) {
            return null;
        }
        long count = weChatUserRepository.countByOpenIdAndAppName(userInfo.getOpenId(), "xyqb");
        if (count > 0) {
            //注意，这里会抛异常（5000/total）,WeChatController中已捕获处理
            return weChatUserRepository.findByOpenIdAndAppName(userInfo.getOpenId(), "xyqb");
        }
        if (null == userInfo.getPhoneNo()) {
            userInfo.setPhoneNo("");
        }
//        // 微信用户首次登录界面, 首先保存userInfo, 跳入到微信注册登录界面
//        // 替换所有,UTF-8编码时4字节的Emoji表情字符
//        String nickName = EmojiUtil.filterUnicode4(userInfo.getNickName());
//        userInfo.setNickName(nickName);
//        // 替换所有,UTF-8编码时4字节的Emoji表情字符
//        String country = EmojiUtil.filterUnicode4(userInfo.getCountry());
//        userInfo.setCountry(country);
//        // 替换所有,UTF-8编码时4字节的Emoji表情字符
//        String prvince = EmojiUtil.filterUnicode4(userInfo.getProvince());
//        userInfo.setProvince(prvince);
//        // 替换所有,UTF-8编码时4字节的Emoji表情字符
//        String city = EmojiUtil.filterUnicode4(userInfo.getCity());
//        userInfo.setCity(city);
        userInfo = userInfo.convertEmoji();
        WechatUserInfo wechatUserInfo = weChatUserRepository.save(userInfo);
        WechatEventMsg wechatEventMsg = WechatEventMsg.builder()
                .userId(wechatUserInfo.getUserId())
                .openId(wechatUserInfo.getOpenId())
                .build();
        applicationEventPublisher.publishEvent(new WechatBindEvent(this, wechatEventMsg));
        return wechatUserInfo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int relateUser(Long userId, String phoneNo, String openId) {
        if (Objects.isNull(userId) || Objects.isNull(openId) || StringUtils.isBlank(openId)) {
            log.error("微信关联失败：非法入参：[service]:userId:{},phoneNo:{},openId:{}", userId, phoneNo, openId);
            return 0;
        }
        // Old - 当前openId的WechatUserInfo
        WechatUserInfo wechatUserInfo = weChatUserRepository.findByOpenIdAndAppName(openId, "xyqb");
        if (Objects.nonNull(wechatUserInfo) && Objects.equals(userId, wechatUserInfo.getUserId()) && Objects.equals(openId, wechatUserInfo.getOpenId())) {
            log.info("微信关联成功：重复关联：跳过：[service]:userId:{},phoneNo:{},openId:{}", userId, phoneNo, openId);
            return 1;
        }
        // 强制解除关联
        int dissociate = weChatUserRepository.dissociateUser(openId, userId, "xyqb");
        if (dissociate < 1) {
            log.error("微信关联失败：解绑条数<1：[service]:userId:{},phoneNo:{},openId:{}", userId, phoneNo, openId);
            throw new WechatRelateUserException("微信关联失败");
        }
        int relate = weChatUserRepository.relateUser(userId, Optional.ofNullable(phoneNo).orElse(""), openId, "xyqb");
        if (relate < 1) {
            log.error("微信关联失败：绑定条数<1：[service]:userId:{},phoneNo:{},openId:{}", userId, phoneNo, openId);
            throw new WechatRelateUserException("微信关联失败");
        }
        WechatEventMsg wechatEventMsg = WechatEventMsg.builder()
                .userId(userId)
                .openId(openId)
                .build();
        applicationEventPublisher.publishEvent(new WechatBindEvent(this, wechatEventMsg));
        // Todo : 如果当前openId已关联其他用户，则解绑成功后要注销其登录session -- 考虑后暂时不执行，影响太大
        log.info("微信关联成功：[service]:userId:{},phoneNo:{},openId:{},dissociate:{},relate:{},Old-WechatUserInfo:{}", userId, phoneNo, openId, dissociate, relate, wechatUserInfo);
        return relate;
    }

    @Override
    public WechatUserInfo findWechatUserInfoByPhoneNo(String phoneNo) {
        if (!ValidationUtil.validatePhoneNo(phoneNo)) {
            return null;
        }
        WechatUserInfo userInfo = weChatUserRepository.findByEncryptedPhoneNoAndAppName(phoneNo, "xyqb");
        if (userInfo == null) {
            userInfo = weChatUserRepository.findByPhoneNoAndAppName(phoneNo, "xyqb");
        }
        return userInfo;
    }

    private String getTokenFromWechatServer(String code) {
        if (StringUtils.isEmpty(code)) {
            return null;
        }
        String finalAccessTokenUrl = String.format(accessTokenUrl, code);
        return httpService.get(finalAccessTokenUrl);
    }

    private String refreshToken(String refreshToken) {
        if (StringUtils.isEmpty(refreshToken)) {
            return null;
        }
        return httpService.get(String.format(refreshTokenUrl, refreshToken));
    }

    /**
     * 按userId查微信信息
     * 此处不应加缓存，否则解绑时不好清除
     *
     * @param userId - 用户标识
     * @return
     * @TODO 2021-10-14 修改 findByUserIdAndAppName 为 findFirstByUserIdAndAppNameOrderByCreatedAtDesc, 原因是存在有多个数据情况
     */
    @Override
    public WechatUserInfo queryByUserId(Long userId) {
        return weChatUserRepository.findFirstByUserIdAndAppNameOrderByCreatedAtDesc(userId, "xyqb");
    }

    /**
     * @param userId
     * @param appName
     * @return
     * @TODO 2021-10-14 修改 findByUserIdAndAppName 为 findFirstByUserIdAndAppNameOrderByCreatedAtDesc, 原因是存在有多个数据情况
     */
    public WechatUserInfo queryByUserId(Long userId, String appName) {
        if (appName == null || "".equals(appName.trim())) {
            appName = "xyqb";
        }
        return weChatUserRepository.findFirstByUserIdAndAppNameOrderByCreatedAtDesc(userId, appName);
    }


    @Override
    public int forbiddenUserWeChat(Long userId) {
        return weChatUserRepository.dissociateByUserIdAndAppName(userId, "xyqb");
    }

    @Override
    public int forbiddenXyqbAndWuxiUserByUserId(Long userId) {
        return weChatUserRepository.forbiddenXyqbAndWuxiUserByUserId(userId);
    }

    @Override
    public List<WechatUserInfo> queryListByUserId(Long userId) {
        return weChatUserRepository.findByUserId(userId);
    }

    @Override
    public List<WechatUserInfo> queryListByUnionId(String unionId) {
        return weChatUserRepository.findByUnionId(unionId);
    }

    @Override
    public List<WechatUserInfo> queryListByUnionIds(List<String> unionIds) {
        return weChatUserRepository.findByUnionIdIn(unionIds);
    }
}
