package com.iformall.controller.sys;

import com.alibaba.fastjson.JSON;
import com.iformall.annotation.SystemControllerLog;
import com.iformall.common.ErrorCode;
import com.iformall.common.Result;
import com.iformall.common.ResultData;
import com.iformall.controller.base.BaseController;
import com.iformall.domain.po.MallUserAction;
import com.iformall.domain.po.MallUserInfo;
import com.iformall.domain.vo.MallUserInfoVo;
import com.iformall.enums.EnumMallUserAction;
import com.iformall.enums.EnumUserAdmin;
import com.iformall.service.MallUserActionService;
import com.iformall.service.MallUserInfoService;
import com.iformall.service.MallUserRoleService;
import com.iformall.shiro.UserSession;
import com.iformall.shiro.UseriFormallToken;
import com.iformall.utils.TOTP;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.DisabledAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import redis.clients.jedis.Protocol;
import redis.clients.util.SafeEncoder;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.*;


@Controller
@RequestMapping("/wechat")
@Slf4j
public class WechatLoginController extends BaseController {
    private String WECHAT_PREV = "LOGIN:OPEN:";

    @Autowired
    private WxMpService wxMpService;

    @Autowired
    private MallUserInfoService mallUserInfoService;

    @Autowired
    private MallUserRoleService mallUserRoleService;

    @Autowired
    private MallUserActionService mallUserActionService;

    @Autowired
    @Qualifier("openRedisTemplate")
    RedisTemplate<String, String> openRedisTemplate;

    @Data
    private class DisplayUserInfo {
        String mallName;
        String userName;
        String userRole;
    }

    @ApiOperation(value = "微信登录")
    @GetMapping("login")
    public void login(HttpServletRequest request, HttpServletResponse response) {
        log.debug("[" + getIpAddr() + "] MallUserInfoController::login");
        String host = request.getHeader("host");
        log.debug("Host: " + host);
        String wechatUrl = wxMpService.buildQrConnectUrl("https://"+host+"/api/wechat/callback", "snsapi_login", "111");
        try {
            response.sendRedirect(wechatUrl);
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    @ApiOperation(value = "微信网页登录回调", notes = "请配置此callback到网页redirect_uri")
    @GetMapping("callback")
    public void getAccessToken(String code, String state, HttpServletRequest request, HttpServletResponse response) {
        String ipaddress = getIpAddr();
        log.debug("[" + ipaddress + "] WechatLoginController::getAccessToken");
        String host = request.getHeader("host");
        log.debug("host: " + host);
        log.debug("code: " + code);
        try {
            WxMpOAuth2AccessToken accessToken = wxMpService.oauth2getAccessToken(code);
            log.info("accessToken: " + accessToken.getAccessToken() + ", openId: " + accessToken.getOpenId() + ", unionId: " + accessToken.getUnionId());

            accessToken = wxMpService.oauth2refreshAccessToken(accessToken.getRefreshToken());
            log.info("accessToken: " + accessToken.getAccessToken() + ", openId: " + accessToken.getOpenId() + ", unionId: " + accessToken.getUnionId());

            // 获取 用户信息
            WxMpUser mpUser = wxMpService.oauth2getUserInfo(accessToken, null);
            if(mpUser != null) {
                log.info(mpUser.toString());
            }

            // check user openid 是否是已授权用户
            List<MallUserInfoVo> userList = mallUserInfoService.getUsersByWebOpenId(accessToken.getOpenId());
            log.info("login user list count " + userList.size());
            if(userList.size() > 0) {
                if(userList.size() == 1) {
                    Map<String, Object> ret = new HashMap<>();
                    // 唯一用户
                    MallUserInfo user = userList.get(0);
                    boolean isLogin = false;
                    try {
                        Subject subject = SecurityUtils.getSubject();
                        UseriFormallToken token = new UseriFormallToken(user.getUsername());
                        subject.login(token);
                        isLogin = true;
                    } catch (UnknownAccountException e) {
                        log.error(e.getMessage());
                        ret.put("code", ErrorCode.USER_IS_EMPTY.getCode());
                        ret.put("message", e.getMessage());
                    } catch (DisabledAccountException e) {
                        log.error(e.getMessage());
                        ret.put("code", ErrorCode.USER_IS_LOCKED.getCode());
                        ret.put("message", e.getMessage());
                    } catch (Exception e) {
                        log.error(e.getMessage());
                        ret.put("code", Result.ERROR);
                        ret.put("message", e.getMessage());
                    }
                    if(isLogin) {
                        MallUserInfo info = (MallUserInfo) SecurityUtils.getSubject().getSession().getAttribute(UserSession.userInfo);
                        info.protectInfos();
                        String menus = mallUserRoleService.getPermissionsByUser(info);
                        if(menus != null) {
                            info.setMenus(menus);
                        }
                        try {
                            String cookieName = URLEncoder.encode(info.getUsername(), "utf-8");
                            Cookie unameCookie = new Cookie("uname", cookieName);
                            unameCookie.setPath("/");
                            unameCookie.setMaxAge(3600);
                            response.addCookie(unameCookie);

                            MallUserAction action = new MallUserAction();
                            action.setTenantId(info.getTenantId());
                            action.setType(EnumMallUserAction.CONTROLLER.getCode());
                            action.setIp(ipaddress);
                            action.setUserId(info.getId());
                            action.setActionDesc("用户微信登录");
                            action.setActionTime(new Date());
                            mallUserActionService.saveOrUpdate(action);
                        } catch (Exception e) {
                            log.error(e.getMessage());
                            ret.put("code", Result.ERROR);
                            ret.put("message", e.getMessage());
                        }
                        Object codeObj = ret.get("code");
                        if(codeObj == null) {
                            log.info("用户登录成功，切换主页");
                            try {
                                response.sendRedirect("https://"+host+"/#/layout");
                            } catch (Exception e) {
                                log.error(e.getMessage());
                            }
                        } else {
                            log.info("用户登录失败-3，返回登录");
                            try {
                                String errCode = URLEncoder.encode(JSON.toJSONString(ret), "utf-8");
                                response.sendRedirect("https://" + host + "/#/login?errcode=" + errCode);
                            } catch (Exception e) {
                                log.error(e.getMessage());
                            }
                        }
                    } else {
                        log.info("用户登录失败-2，返回登录");
                        try {
                            String errCode = URLEncoder.encode(JSON.toJSONString(ret), "utf-8");
                            response.sendRedirect("https://" + host + "/#/login?errcode="+errCode);
                        } catch (Exception e) {
                            log.error(e.getMessage());
                        }
                    }
                } else {
                    // 跳转登录选择页面
                    String openId = accessToken.getOpenId();
                    String key = TOTP.generateWechatOpen(openId);
                    String openKey = WECHAT_PREV + key;
                    Boolean isAbsent = openRedisTemplate.<Boolean>execute(new RedisCallback<Boolean>() {
                        @Override
                        public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                            RedisSerializer valueSerializer = openRedisTemplate.getValueSerializer();
                            RedisSerializer keySerializer = openRedisTemplate.getKeySerializer();
                            Object obj = connection.execute("set", keySerializer.serialize(openKey),
                                    valueSerializer.serialize(openId),
                                    SafeEncoder.encode("NX"),
                                    SafeEncoder.encode("EX"),
                                    Protocol.toByteArray(60)); // 60s 过期时间
                            return obj != null;
                        }
                    });
                    List<DisplayUserInfo> users = new ArrayList<DisplayUserInfo>();
                    for(MallUserInfoVo user: userList) {
                        DisplayUserInfo disUser = new DisplayUserInfo();
                        disUser.setMallName(user.getMallName());
                        disUser.setUserName(user.getUsername());
                        disUser.setUserRole(user.getRoleName());
                        users.add(disUser);
                    }
                    String usersStr = JSON.toJSONString(users);
                    log.info(usersStr);
                    try {
                        usersStr = URLEncoder.encode(usersStr, "utf-8");
                        response.sendRedirect("https://" + host + "/#/loginselect?key=" + key + "&list=" + usersStr);
                    } catch (Exception e) {
                        log.error(e.getMessage());
                    }
                }
            } else {
                log.info("用户登录失败-1，返回登录");
                // 跳转登录页面
                Map<String, Object> ret = new HashMap<>();
                ret.put("code", ErrorCode.WECHAT_LOGIN_NOT_BIND.getCode());
                ret.put("message", ErrorCode.WECHAT_LOGIN_NOT_BIND.getMessage());
                try {
                    String errCode = URLEncoder.encode(JSON.toJSONString(ret), "utf-8");
                    response.sendRedirect("https://" + host + "/#/login?errcode="+errCode);
                } catch (Exception e) {
                    log.error(e.getMessage());
                }
            }
        } catch (WxErrorException e) {
            log.error(e.getMessage());
        }
    }

    @ApiOperation(value = "微信用户登录")
    @GetMapping("weChatUserLogin")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "key", value = "key", dataType = "String", paramType = "query", required = true),
            @ApiImplicitParam(name = "userName", value = "userName", dataType = "String", paramType = "query", required = true)})
    public ResultData weChatUserLogin(String key, String userName, HttpServletRequest request, HttpServletResponse response) {
        String ipaddress = getIpAddr();
        log.debug("[" + ipaddress + "] MallUserInfoController::weChatUserLogin");
        String host = request.getHeader("host");
        log.debug("Host: " + host);
        if(StringUtils.isBlank(userName)) {
            log.error("请选择要登录的用户");
            return new ResultData(ErrorCode.WECHAT_LOGIN_USER_SELECT);
        }

        String openKey = WECHAT_PREV + key;
        // 限时时间内查找到此用户openId
        if (openRedisTemplate.hasKey(openKey)){
            log.info(openKey + " - 找不到");
            String openId = openRedisTemplate.opsForValue().get(openKey);
            openRedisTemplate.delete(openKey);
            log.info("KEY: " + openKey + " deleted");

            MallUserInfo userInfo = mallUserInfoService.getByUsername(userName);
            if(userInfo.getWebOpenId().equals(openId)) {
                boolean isLogin = false;
                try {
                    Subject subject = SecurityUtils.getSubject();
                    UseriFormallToken token = new UseriFormallToken(userInfo.getUsername());
                    subject.login(token);
                    MallUserInfo info = (MallUserInfo) SecurityUtils.getSubject().getSession().getAttribute(UserSession.userInfo);
                    info.protectInfos();
                    String menus = mallUserRoleService.getPermissionsByUser(info);
                    if(menus != null) {
                        info.setMenus(menus);
                    }
                    // 登录cookie
                    String cookieName = URLEncoder.encode(info.getUsername(), "utf-8");
                    Cookie unameCookie = new Cookie("uname", cookieName);
                    unameCookie.setPath("/");
                    unameCookie.setMaxAge(3600);
                    response.addCookie(unameCookie);

                    MallUserAction action = new MallUserAction();
                    action.setTenantId(info.getTenantId());
                    action.setType(EnumMallUserAction.CONTROLLER.getCode());
                    action.setIp(ipaddress);
                    action.setUserId(info.getId());
                    action.setActionDesc("用户微信登录");
                    action.setActionTime(new Date());
                    mallUserActionService.saveOrUpdate(action);

                    return new ResultData();
                }  catch (UnknownAccountException e) {
                    log.error(e.getMessage());
                    return new ResultData(ErrorCode.USER_IS_EMPTY.getCode(), e.getMessage());
                } catch (DisabledAccountException e) {
                    log.error(e.getMessage());
                    return new ResultData(ErrorCode.USER_IS_LOCKED.getCode(), e.getMessage());
                } catch (Exception e) {
                    log.error(e.getMessage());
                    return new ResultData(Result.ERROR, e.getMessage());
                }
            } else {
                // 登录失败
                log.error("微信登录失败，用户微信未绑定:" + openId);
                return new ResultData(ErrorCode.WECHAT_LOGIN_NOT_BIND);
            }
        }
        log.error("微信登录失败，KEY已过期: "+ key);
        return new ResultData(ErrorCode.WECHAT_LOGIN_KEY_OVERTIME);
    }

    @ApiOperation(value = "微信第三方登录绑定", notes = "请配置此callback到网页redirect_uri")
    @GetMapping("bindWebOpenId")
    @SystemControllerLog(description = "微信第三方登录绑定")
    public void userBindWebOpenId(String code, String state, HttpServletRequest request, HttpServletResponse response) {
        log.debug("[" + getIpAddr() + "] WechatLoginController::bindWebOpenId");
        String host = request.getHeader("host");
        String errCode = null;
        try {
            WxMpOAuth2AccessToken accessToken = wxMpService.oauth2getAccessToken(code);
            log.debug("accessToken: " + accessToken.getAccessToken() + ", openId: " + accessToken.getOpenId() + ", unionId: " + accessToken.getUnionId());

            // 获取 用户信息
            WxMpUser mpUser = wxMpService.oauth2getUserInfo(accessToken, null);
            if(mpUser != null) {
                log.debug(mpUser.toString());
            }

            String uname = state;
            MallUserInfo user = mallUserInfoService.getByUsername(uname);
            if(user != null) {
                user.setWebOpenId(accessToken.getOpenId());
                mallUserInfoService.updateWebOpenId(user);
                log.debug("https://" + host + "/#/layout");
                response.sendRedirect("https://" + host + "/#/layout");
            } else {
                log.debug("https://" + host + "/#/layout?errcode=绑定失败");
                errCode = URLEncoder.encode("绑定失败", "utf-8");
                response.sendRedirect("https://" + host + "/#/layout?errcode="+errCode);
            }
        } catch (WxErrorException e) {
            log.error(e.getMessage());
            errCode = e.getMessage();
        } catch (IOException e) {
            log.error(e.getMessage());
            errCode = e.getMessage();
        }

        if(StringUtils.isNotBlank(errCode)) {
            try {
                errCode = URLEncoder.encode(errCode, "utf-8");
                response.sendRedirect("https://" + host + "/#/layout?errcode="+errCode);
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }
    }

    @ApiOperation(value = "微信第三方登录解绑", notes = "请配置此callback到网页redirect_uri")
    @GetMapping("cleanWebOpenId")
    @SystemControllerLog(description = "微信第三方登录解绑")
    public ResultData cleanWebOpenId(@ModelAttribute MallUserInfo userInfo) {
        log.debug("[" + getIpAddr() + "] WechatLoginController::cleanWebOpenId");
        MallUserInfo user = getUser();
        if(userInfo == null) {
            return new ResultData(ErrorCode.SYS_PARAMETER_NOT_NULL);
        }
        if(StringUtils.isBlank(userInfo.getTenantId())) {
            userInfo.setTenantId(user.getTenantId());
        }
        if(userInfo.getId() == null && userInfo.getUsername() == null) {
            return new ResultData(ErrorCode.SYS_PARAMETER_NOT_NULL);
        }
        // 只有本人及系统管理员可以解绑微信
        if(userInfo.getId().equals(user.getId())
                || user.getIsAdmin().equals(EnumUserAdmin.ADMIN.getCode())) {
            mallUserInfoService.cleanWebOpenId(userInfo);
            return new ResultData();
        } else {
            return new ResultData(ErrorCode.USER_NO_PERMISSION);
        }

    }
}
