package com.sien.common.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sien.common.entity.AppUser;
import com.sien.common.enums.StateEnum;
import com.sien.common.mapper.AppUserMapper;
import com.sien.common.param.AppUserPageParam;
import com.sien.common.param.app.AppSmsRegisterParam;
import com.sien.common.param.app.DeviceTokenParam;
import com.sien.common.service.AppUserService;
import com.sien.common.service.DonationRecordService;
import com.sien.common.vo.AppUserQueryVo;
import com.sien.common.vo.DonationRankAndTotal;
import com.sien.common.vo.app.LoginAppUserTokenVo;
import com.sien.common.vo.app.MyInfoVo;
import io.geekidea.springbootplus.config.properties.JwtProperties;
import io.geekidea.springbootplus.config.properties.SpringBootPlusProperties;
import io.geekidea.springbootplus.config.properties.WxMpProperties;
import io.geekidea.springbootplus.framework.common.api.ApiCode;
import io.geekidea.springbootplus.framework.common.api.ApiResult;
import io.geekidea.springbootplus.framework.common.service.impl.BaseServiceImpl;
import io.geekidea.springbootplus.framework.core.pagination.PageInfo;
import io.geekidea.springbootplus.framework.core.pagination.Paging;
import io.geekidea.springbootplus.framework.shiro.cache.AppLoginRedisService;
import io.geekidea.springbootplus.framework.shiro.jwt.JwtToken;
import io.geekidea.springbootplus.framework.shiro.jwt.realm.LoginClientTypeEnum;
import io.geekidea.springbootplus.framework.shiro.util.JwtUtil;
import io.geekidea.springbootplus.framework.shiro.util.SaltUtil;
import io.geekidea.springbootplus.framework.shiro.vo.LoginUserVo;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

;

/**
 * APP用户 服务实现类
 *
 * @author wei
 * @since 2020-09-23
 */
@Slf4j
@Service
public class AppUserServiceImpl extends BaseServiceImpl<AppUserMapper, AppUser> implements AppUserService {

    @Autowired
    private WxMpProperties properties;

    @Autowired
    private RedisTemplate redisTemplate;

    @Lazy
    @Autowired
    private AppLoginRedisService appLoginRedisService;

    @Lazy
    @Autowired
    private JwtProperties jwtProperties;

    @Autowired
    private SpringBootPlusProperties springBootPlusProperties;

    @Autowired
    private AppUserMapper appUserMapper;

    @Autowired
    private DonationRecordService donationRecordService;


    @Override
    public boolean hasUserByPhoneNumer(String phoneArea, String phone) {
        Integer selectCount = appUserMapper.selectCount(new QueryWrapper<AppUser>(
                new AppUser().setPhoneArea(phoneArea).setPhone(phone)
        ));
        return selectCount > 0;
    }

//    @Override
//    public ApiResult<LoginAppUserTokenVo> register(AppSmsRegisterParam appSmsRegisterParam, String language) {
////        JwtToken jwtToken = (JwtToken) SecurityUtils.getSubject().getPrincipal();
//
//        // 校验短信验证码是否正确
//
//        return null;
//    }

    @Override
    public ApiResult<LoginAppUserTokenVo> login(AppSmsRegisterParam loginParam, String language, Boolean hasRegister) {

        // 从数据库中获取登录用户信息
        AppUser appUser = appUserMapper.selectOne(new QueryWrapper<>(
                new AppUser().setPhoneArea(loginParam.getPhoneArea()).setPhone(loginParam.getPhone()))
        );

        if (appUser == null) {
            log.error("登录失败,用户名或密码错误 loginParam:{}", loginParam);
            return ApiResult.fail(ApiCode.USER_NOT_FOUND, language);
        }
        if (StateEnum.DISABLE.getCode().equals(appUser.getState())) {
//            throw new AuthenticationException("账号已禁用");
            log.error("登录失败,账号已禁用 loginParam:{}", loginParam);
            return ApiResult.fail(ApiCode.USER_NOT_FOUND, language);

        }
        LoginAppUserTokenVo loginAppUserTokenVo = getLoginAppUserTokenVo(hasRegister, appUser);
        return ApiResult.ok(loginAppUserTokenVo);

    }

    private LoginAppUserTokenVo getLoginAppUserTokenVo(Boolean hasRegister, AppUser appUser) {
        //    将系统用户对象转换成登录用户对象
        LoginUserVo loginSysUserVo = new LoginUserVo();

        loginSysUserVo.setId(appUser.getId());
        loginSysUserVo.setUsername(appUser.getId().toString());

        // 获取数据库中保存的盐值
        String newSalt = SaltUtil.getSalt(appUser.getSalt(), jwtProperties);

        // 生成token字符串并返回
        Long expireSecond = jwtProperties.getExpireSecond();
        String token = JwtUtil.generateToken(appUser.getId().toString(), newSalt, Duration.ofSeconds(expireSecond));
        log.debug("token:{}", token);

        // 创建AuthenticationToken
        JwtToken jwtToken = JwtToken.build(token, appUser.getId().toString(), appUser.getId(), newSalt, expireSecond, LoginClientTypeEnum.APP.getType(), null);

        boolean enableShiro = springBootPlusProperties.getShiro().isEnable();
        if (enableShiro) {
            // 从SecurityUtils里边创建一个 subject
            Subject subject = SecurityUtils.getSubject();
            // 执行认证登录
            subject.login(jwtToken);
        } else {
            log.warn("未启用Shiro");
        }

        // 缓存登录信息到Redis
        appLoginRedisService.cacheLoginInfo(jwtToken, loginSysUserVo);
        log.debug("登录成功,id:{}", appUser.getId().toString());

        // 缓存登录信息到redis
        String tokenSha256 = DigestUtils.sha256Hex(token);
        redisTemplate.opsForValue().set(tokenSha256, loginSysUserVo, 1, TimeUnit.DAYS);

        // 返回token和登录用户信息对象
        LoginAppUserTokenVo loginAppUserTokenVo = new LoginAppUserTokenVo();
        BeanUtils.copyProperties(appUser, loginAppUserTokenVo);
        loginAppUserTokenVo.setToken(token);
//        loginAppUserTokenVo.setId(appUser.getId());
        loginAppUserTokenVo.setHasRegister(hasRegister);
        return loginAppUserTokenVo;
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveAppUser(AppUser appUser) throws Exception {
        return super.save(appUser);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean updateAppUser(AppUser appUser) throws Exception {
        return super.updateById(appUser);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean updateDeviceToken(DeviceTokenParam deviceTokenParam, int deviceType) throws Exception {
        AppUser appUser = new AppUser();
//        appUser.setDeviceToken(deviceTokenParam.getDeviceToken());
//        appUser.setDeviceType(deviceType);

        JwtToken jwtToken = (JwtToken) SecurityUtils.getSubject().getPrincipal();

        appUser.setId(jwtToken.getUserId());

        // 重置redis中的token


        return this.updateAppUser(appUser);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean deleteAppUser(Long id) throws Exception {
        return super.removeById(id);
    }

    @Override
    public AppUserQueryVo getAppUserById(Long id) throws Exception {
        return appUserMapper.getAppUserById(id);
    }

    @Override
    public MyInfoVo getMyInfo() throws Exception {
        JwtToken jwtToken = (JwtToken) SecurityUtils.getSubject().getPrincipal();
        AppUserQueryVo appUserById = appUserMapper.getAppUserById(jwtToken.getUserId());
        DonationRankAndTotal donationRankAndTotal = donationRecordService.getDonationRankAndTotal(jwtToken.getUserId());

        if (donationRankAndTotal == null) {

            donationRankAndTotal =new DonationRankAndTotal();
            donationRankAndTotal.setDrTotal(new BigDecimal("0"));
            donationRankAndTotal.setRank(9999L);
        }
        MyInfoVo myInfoVo = new MyInfoVo();
        myInfoVo.setAppUserQueryVo(appUserById);
        myInfoVo.setDonationRankAndTotal(donationRankAndTotal);

        return myInfoVo;
    }

    private WxMpService getWxMpService() {
        // 代码里 getConfigs()处报错的同学，请注意仔细阅读项目说明，你的IDE需要引入lombok插件！！！！
        final List<WxMpProperties.MpConfig> configs = this.properties.getConfigs();
        if (configs == null) {
            throw new RuntimeException("添加下相关配置，注意别配错了！");
        }
        WxMpService service = new WxMpServiceImpl();
        service.setMultiConfigStorages(configs
                .stream().map(a -> {
                    WxMpDefaultConfigImpl configStorage;
//
                    configStorage = new WxMpDefaultConfigImpl();
                    configStorage.setAppId(a.getAppId());
                    configStorage.setSecret(a.getSecret());
                    configStorage.setToken(a.getToken());
                    configStorage.setAesKey(a.getAesKey());
                    return configStorage;
                }).collect(Collectors.toMap(WxMpDefaultConfigImpl::getAppId, a -> a, (o, n) -> o)));
        return service;
    }

    @Override
    public ApiResult<LoginAppUserTokenVo> checkWechatUserBinding(String code) {

        WxOAuth2AccessToken accessToken = null;
        try {
            accessToken = getWxMpService().getOAuth2Service().getAccessToken(code);
        } catch (WxErrorException e) {
//            e.printStackTrace();
            log.debug(e.getMessage());
            return ApiResult.result(ApiCode.USER_WECHAT_CODE, null);
        }

        // 查询openId是否被绑定
        String openId = accessToken.getOpenId();
        AppUser appUserByOpenId = super.getOne(new QueryWrapper<AppUser>().lambda().eq(AppUser::getWechatOpenId, openId));

        // 第一次点击 没有绑定的情况
        if (appUserByOpenId == null) {

            AppUser appUsernew = new AppUser();

            // 从微信获取用户信息
            WxOAuth2UserInfo wechatUserInfo = null;
            try {
                wechatUserInfo = getWxMpService().getOAuth2Service().getUserInfo(accessToken, null);
            } catch (WxErrorException e) {
                log.debug(e.getMessage());
                return ApiResult.result(ApiCode.USER_WECHAT_CODE, null);
            }
            appUsernew.setWechatOpenId(openId);
            appUsernew.setNickname(wechatUserInfo.getNickname());
            appUsernew.setHead(wechatUserInfo.getHeadImgUrl());
            appUsernew.setLocation(wechatUserInfo.getCountry() + wechatUserInfo.getProvince() + wechatUserInfo.getCity());
            appUsernew.setWechatOpenId(wechatUserInfo.getOpenid());

            // 先保存至数据库
            super.save(appUsernew);

            // 返回告诉前端没有绑定过
            LoginAppUserTokenVo loginAppUserTokenVo = new LoginAppUserTokenVo();
            loginAppUserTokenVo.setHasRegister(Boolean.FALSE);
            loginAppUserTokenVo.setOpenId(openId);
            return ApiResult.ok(loginAppUserTokenVo);
        }

        // 点击过 但没有绑定手机
        LoginAppUserTokenVo loginAppUserTokenVo;
        if (StringUtils.isEmpty(appUserByOpenId.getPhone())) {
            // 返回告诉前端没有绑定过
            loginAppUserTokenVo = new LoginAppUserTokenVo();
            loginAppUserTokenVo.setHasRegister(Boolean.FALSE);
            loginAppUserTokenVo.setOpenId(openId);

            return ApiResult.ok(loginAppUserTokenVo);


        } else {
            // 有绑定直接返回用户token
            loginAppUserTokenVo = getLoginAppUserTokenVo(Boolean.TRUE, appUserByOpenId);
        }
        return ApiResult.ok(loginAppUserTokenVo);

    }


    @Override
    public Paging<AppUserQueryVo> getAppUserPageList(AppUserPageParam appUserPageParam) throws Exception {
        Page<AppUserQueryVo> page = new PageInfo<>(appUserPageParam, OrderItem.desc(getLambdaColumn(AppUser::getCreateTime)));
        IPage<AppUserQueryVo> iPage = appUserMapper.getAppUserPageList(page, appUserPageParam);
        return new Paging<AppUserQueryVo>(iPage);
    }
}
