package com.css.simulation.oauth.client.controller;

import api.common.pojo.common.ResponseBodyVO;
import api.common.pojo.constants.DictConstants;
import api.common.pojo.param.sign.SignRefreshParameter;
import api.common.pojo.param.sign.SignSingleParameter;
import api.common.pojo.param.sign.SignUsernameParameter;
import api.common.pojo.po.log.LogLoginPO;
import api.common.pojo.po.system.UserPO;
import api.common.pojo.vo.sign.SimulationTokenVO;
import api.common.pojo.vo.system.UserVO;
import api.common.util.*;
import com.css.simulation.oauth.client.configuration.oauth.OauthParameter;
import com.css.simulation.oauth.client.mapper.LogLoginMapper;
import com.css.simulation.oauth.client.mapper.UserMapper;
import com.css.simulation.oauth.client.util.EncodeUtil;
import com.css.simulation.oauth.client.util.IpUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.util.List;
import java.util.Set;

//@RestController 不能用 RestController，重定向接口 /entry 不能
@Controller
@RequestMapping("/sign")
@Slf4j
public class SignController {

    @Resource
    private UserMapper userMapper;

    @Resource
    private LogLoginMapper logLoginMapper;

    //    @Resource
//    private CloseableHttpClient closeableHttpClient;
//    @Resource
//    private RequestConfig requestConfig;
    @Resource
    private OauthParameter oauthParameter;

    @Autowired
    private StringRedisTemplate redisTemplate;


    @RequestMapping("/entry")
    @SneakyThrows
    public String entry(@RequestParam("ticket") String ticket) {
        log.info("接收到的 ticket 为：" + ticket);
        String encodeTicket = EncodeUtil.utf8(ticket);
        log.info("ticket 编码之后为：" + encodeTicket);
        log.info("重定向地址为：" + oauthParameter.getZoogooyRedirectUri());
        String encodeRedirect = EncodeUtil.utf8(oauthParameter.getZoogooyRedirectUri());
        log.info("重定向地址编码之后为：" + encodeRedirect);

        String zoogooyAuthorizeUrl = oauthParameter.getZoogooyAuthorizeUri() +
                "?appid=" + oauthParameter.getZoogooyAppid() +
                "&redirect_uri=" + encodeRedirect +
                "&ticket=" + encodeTicket +
                "&response_type=code" +
                "&scope=snsapi_userinfo";
        log.info("需要访问的授权地址为：" + zoogooyAuthorizeUrl);
        return "redirect:" + zoogooyAuthorizeUrl;
    }

    /**
     * 单点登录
     * 众工业平台：http://icvdc.china-icv.cn/
     *
     * @return token 信息
     */
    @RequestMapping("/single")
    @SneakyThrows
    @ResponseBody
    public ResponseBodyVO<SimulationTokenVO> single(@RequestBody SignSingleParameter signSingleParameter, HttpServletRequest request) {

        String code = signSingleParameter.getCode();
        String ticket = signSingleParameter.getTicket();

        String simulationToken;
        String userId = "";
        String username = null;
        // 首先判断 redis 里是否存在
        if (Boolean.TRUE.equals(redisTemplate.hasKey(("code:" + code + ":ticket:" + ticket)))) {
            simulationToken = redisTemplate.opsForValue().get("code:" + code + ":ticket:" + ticket);
        } else {

            String encodeCode = EncodeUtil.utf8(code);
            String encodeTicket = EncodeUtil.utf8(ticket);
            log.info("接收到的 code 为：" + code);
            log.info("code 编码之后为：" + encodeCode);
            log.info("接收到的 ticket 为：" + ticket);
            log.info("ticket 编码之后为：" + encodeTicket);
            String zoogooyTokenUrl = oauthParameter.getZoogooyTokenUri() +
                    "?appid=" + oauthParameter.getZoogooyAppid() +
                    "&secret=" + oauthParameter.getZoogooyAppSecret() +
                    "&code=" + encodeCode +
                    "&grant_type=authorization_code";
            log.info("根据编码之后的 code 获取众工业平台 access_token 的地址为：" + zoogooyTokenUrl);
            String zoogooyTokenJson = HttpUtil.get(zoogooyTokenUrl);
            log.info("根据编码之后的 code 获取众工业平台 access_token 的结果为：" + zoogooyTokenJson);
            ObjectMapper objectMapper1 = new ObjectMapper();
            JsonNode tokenRoot = objectMapper1.readTree(zoogooyTokenJson);
            if (!tokenRoot.path("success").asBoolean()) {
                throw new RuntimeException(tokenRoot.path("message").asText());
            }
            log.info("众工业平台 token 信息为：" + zoogooyTokenJson);
        /*
        {
          "data" : {
            "scope" : "snsapi_userinfo",
            "expires_in_sec" : 60,
            "access_token" : "30b26a0824a1456f96bfc151b7bb5356",
            "openid" : "6c7a11cebMkd0qvhpskKX1nRzAicfH9xePJ945DOG+7j9KOB/9M=",
            "appid" : "2af6f44d98104dc5adcbfb49809ff9d5",
            "create_time" : "2022-03-25 17:50:49",
            "unionid" : "E2c47RsObMkd0qvhpskKX1nRzAicfH9xePJ945DOG+7j9KOB/9M="
          },
          "success" : true,
          "message" : "ok",
          "code" : 1,
          "nowTime" : "2022-03-25 17:50:52"
        }
         */

            String accessToken = tokenRoot.path("data").path("access_token").asText();
            String encodeAccessToken = EncodeUtil.utf8(accessToken);
            String openid = tokenRoot.path("data").path("openid").asText();
            String encodeOpenid = EncodeUtil.utf8(openid);

            log.info("众工业平台 access_token 为：" + accessToken);
            log.info("众工业平台 access_token 编码之后为：" + encodeAccessToken);
            log.info("众工业平台 openid 为：" + openid);
            log.info("众工业平台 openid 编码之后为：" + encodeOpenid);
            String zoogooyUserUrl = oauthParameter.getZoogooyUserUri() +
                    "?access_token=" + encodeAccessToken +
                    "&openid=" + encodeOpenid +
                    "&ticket=" + encodeTicket;
            log.info("根据编码之后的 access_token、openid、ticket 获取众工业平台用户信息：" + zoogooyUserUrl);
        /*
        {
          "data" : {
            "loginName" : "13389957835",
            "photoId" : "1648195226865/908fa0ec08fa513d7e3510cd40c346f1b3fbd9ba.jpeg",
            "userType" : "personal",
            "nickname" : "尼卡",
            "openid" : "6c7a11cebMkd0qvhpskKX1nRzAicfH9xePJ945DOG+7j9KOB/9M="
          },
          "success" : true,
          "message" : "ok",
          "code" : 1,
          "nowTime" : "2022-03-28 11:11:09"
        }
         */
            String zoogooyUserJson = HttpUtil.get(zoogooyUserUrl);
            ObjectMapper objectMapper2 = new ObjectMapper();
            JsonNode userRoot = objectMapper2.readTree(zoogooyUserJson);
            if (!userRoot.path("success").asBoolean()) {
                throw new RuntimeException("获取众工业平台用户信息出错：" + zoogooyUserJson);
            }
            log.info("众工业平台用户信息为：" + zoogooyUserJson);

            username = "zoogooy-" + userRoot.path("data").path("loginName").asText();
            String nickname = userRoot.path("data").path("nickname").asText();
            String password = EncryptUtil.getLowerMD5(oauthParameter.getSimulationDefaultPassword());

            //3 使用 union_id 查询数据库，是否已在仿真平台存在该用户
            UserPO oldUser = userMapper.selectByOpenIdIgnoreDelete(openid);
            userId = "";
            if (oldUser == null) {   //3-1 仿真平台不存在用户，直接创建新的
                UserPO newUser = new UserPO();
                newUser.setId(StringUtil.getRandomUUID());
                newUser.setUsername(username);
                newUser.setNickname(nickname);
                newUser.setOpenid(openid);
                newUser.setPassword(password);
                newUser.setCompany("国汽");
                newUser.setRoleCode(DictConstants.ROLE_CODE_SUBUESR);// 默认为索为普通账户的子账户
                newUser.setUseType(DictConstants.USER_TYPE_PUBLIC);// 索为账户默认为共享用户
                newUser.setVisible("1");//启用登录功能
                newUser.setCreateUserId(oauthParameter.getSimulationZoogooyUserId());
                newUser.setModifyUserId(oauthParameter.getSimulationZoogooyUserId());
                newUser.setCreateTime(TimeUtil.getNowForMysql());
                newUser.setModifyTime(TimeUtil.getNowForMysql());
                newUser.setIsDeleted("0");
                userMapper.insert(newUser);
                userId = newUser.getId();
                log.info("成功插入新用户信息：" + newUser);
            } else {
                if ("1".equals(oldUser.getIsDeleted())) { //3-2 仿真平台存在删除状态用户，改为未删除
                    userMapper.updateIsDeleted(openid, "0");
                }
                //3-3 仿真平台用户存在未删除用户，放行。不会每次都更新，防止在仿真云平台更新的信息被众工业覆盖
                username = oldUser.getUsername();
                password = oldUser.getPassword();
                userId = oldUser.getId();
            }

            // -------------------------------- 这里需要用到前面创建的用户信息，所以不使用事务 --------------------------------
            //4 根据仿真平台用户名密码颁发仿真平台 token，返回给前端
            String simulationTokenUrl = oauthParameter.getSimulationTokenUri() +
                    "?grant_type=password" +
                    "&client_id=" + oauthParameter.getSimulationClientId() +
                    "&client_secret=" + oauthParameter.getSimulationClientSecret() +
                    "&username=" + username +
                    "&password=" + password;
            log.info("获取仿真云平台 token 信息：" + simulationTokenUrl);
            simulationToken = HttpUtil.get(simulationTokenUrl);
            log.info("仿真云平台 token 信息为：" + simulationToken);
            redisTemplate.opsForValue().set("code:" + code + ":ticket:" + ticket, simulationToken, Duration.ofMinutes(30L));
        }


        SimulationTokenVO simulationTokenVO = JsonUtil.jsonToBean(simulationToken, SimulationTokenVO.class);
        //登录日志
        LogLoginPO po = new LogLoginPO();
        po.setId(StringUtil.getRandomUUID());
        po.setCreateTime(TimeUtil.getNowForMysql());
        String remoteAddress = IpUtil.getRemoteAddress(request);
        po.setIp(remoteAddress);
        po.setUserId(userId);
        po.setUsername(username);
        po.setState(1);
        logLoginMapper.insertLog(po);
        return new ResponseBodyVO<>(ResponseBodyVO.Response.SUCCESS, simulationTokenVO);
    }


    /**
     * 用户名密码登录
     *
     * @return token 信息
     */
    @RequestMapping("/username")
    @SneakyThrows
    @ResponseBody
    public ResponseBodyVO<SimulationTokenVO> username(@RequestBody @Validated SignUsernameParameter signSingleParameter, HttpServletRequest request) {
        log.info("登录参数为：" + signSingleParameter);
        String username = signSingleParameter.getUsername();
        String password = signSingleParameter.getPassword();
        //密码加密
        password = EncryptUtil.getLowerMD5(password);
        UserVO userVO = userMapper.selectByUsername(username);

        //登录日志
        LogLoginPO po = new LogLoginPO();
        po.setId(StringUtil.getRandomUUID());
        po.setCreateTime(TimeUtil.getNowForMysql());
        String remoteAddress = IpUtil.getRemoteAddress(request);
        po.setIp(remoteAddress);

        //登录逻辑判断
        if (userVO == null) {
            return new ResponseBodyVO<>(ResponseBodyVO.Response.CLIENT_FAILURE, "用户名或密码错误。");
        } else if (!username.equals(userVO.getUsername()) && username.equalsIgnoreCase(userVO.getUsername())) {
            return new ResponseBodyVO<>(ResponseBodyVO.Response.CLIENT_FAILURE, "用户名大小写格式有问题。");
        } else if (!"1".equals(userVO.getVisible())) {
            return new ResponseBodyVO<>(ResponseBodyVO.Response.CLIENT_FAILURE, "账户已停用。");
        } else if (!userVO.getPassword().equals(password)) {
            //记录登录错误日志
            po.setUserId(userVO.getId());
            po.setUsername(userVO.getUsername());
            po.setState(0);
            logLoginMapper.insertLog(po);
            return new ResponseBodyVO<>(ResponseBodyVO.Response.CLIENT_FAILURE, "用户名或密码错误!");
        }
        //根据登录日志判断是否锁定账户
        List<LogLoginPO> log = logLoginMapper.selectLogByUserId(userVO.getId(), oauthParameter.getSimulationLoginRepeatHours(), oauthParameter.getSimulationLoginRepeatCount());
        if (ObjectUtil.isNotNull(log) && log.size() >= oauthParameter.getSimulationLoginRepeatCount()) {
            int sum = log.stream().mapToInt(m -> m.getState()).sum();
            if (sum <= 0) {
                return new ResponseBodyVO<>(ResponseBodyVO.Response.CLIENT_FAILURE, "登录失败次数过多，账户被锁定，请" + oauthParameter.getSimulationLoginRepeatHours() + "小时后重新登录!");
            }
        }
       /* //登录逻辑判断
        if (userVO == null ) {
            return new ResponseBodyVO<>(ResponseBodyVO.Response.CLIENT_FAILURE, "用户名或密码错误!");
        }else if(!"1".equals(userVO.getVisible())){
            return new ResponseBodyVO<>(ResponseBodyVO.Response.CLIENT_FAILURE, "账户已停用!");
        }else if(!userVO.getPassword().equals(password)) {
            //记录登录错误日志
            po.setUserId(userVO.getId());
            po.setUsername(userVO.getUsername());
            po.setState(0);
            logLoginMapper.insertLog(po);
            return new ResponseBodyVO<>(ResponseBodyVO.Response.CLIENT_FAILURE, "用户名或密码错误!");
        }*/
        //3 根据仿真平台用户名密码颁发仿真平台 token，返回给前端
        String simulationTokenUrl = oauthParameter.getSimulationTokenUri() +
                "?grant_type=password" +
                "&client_id=" + oauthParameter.getSimulationClientId() +
                "&client_secret=" + oauthParameter.getSimulationClientSecret() +
                "&username=" + username +
                "&password=" + password;
        String simulationToken = HttpUtil.get(simulationTokenUrl);
        SimulationTokenVO simulationTokenVO = JsonUtil.jsonToBean(simulationToken, SimulationTokenVO.class);
        //记录登录成功日志
        po.setUserId(userVO.getId());
        po.setUsername(userVO.getUsername());
        po.setState(1);
        logLoginMapper.insertLog(po);
        return new ResponseBodyVO<>(ResponseBodyVO.Response.SUCCESS, simulationTokenVO);
    }


    /**
     * 用户名密码登录
     *
     * @return token 信息
     */
    @RequestMapping("/username_for_scheduler")
    @SneakyThrows
    @ResponseBody
    public ResponseBodyVO<SimulationTokenVO> usernameForScheduler(@RequestBody @Validated SignUsernameParameter signSingleParameter) {
        log.info("登录参数为：" + signSingleParameter);
        String username = signSingleParameter.getUsername();
        String password = signSingleParameter.getPassword();
        //密码加密
        password = EncryptUtil.getLowerMD5(password);
        UserVO userVO = userMapper.selectByUsername(username);

        //登录逻辑判断
        if (userVO == null) {
            return new ResponseBodyVO<>(ResponseBodyVO.Response.CLIENT_FAILURE, "用户名或密码错误。");
        } else if (!username.equals(userVO.getUsername()) && username.equalsIgnoreCase(userVO.getUsername())) {
            return new ResponseBodyVO<>(ResponseBodyVO.Response.CLIENT_FAILURE, "用户名大小写格式有问题。");
        } else if (!"1".equals(userVO.getVisible())) {
            return new ResponseBodyVO<>(ResponseBodyVO.Response.CLIENT_FAILURE, "账户已停用。");
        }
        //根据登录日志判断是否锁定账户
        List<LogLoginPO> log = logLoginMapper.selectLogByUserId(userVO.getId(), oauthParameter.getSimulationLoginRepeatHours(), oauthParameter.getSimulationLoginRepeatCount());
        if (ObjectUtil.isNotNull(log) && log.size() >= oauthParameter.getSimulationLoginRepeatCount()) {
            int sum = log.stream().mapToInt(m -> m.getState()).sum();
            if (sum <= 0) {
                return new ResponseBodyVO<>(ResponseBodyVO.Response.CLIENT_FAILURE, "登录失败次数过多，账户被锁定，请" + oauthParameter.getSimulationLoginRepeatHours() + "小时后重新登录!");
            }
        }
       /* //登录逻辑判断
        if (userVO == null ) {
            return new ResponseBodyVO<>(ResponseBodyVO.Response.CLIENT_FAILURE, "用户名或密码错误!");
        }else if(!"1".equals(userVO.getVisible())){
            return new ResponseBodyVO<>(ResponseBodyVO.Response.CLIENT_FAILURE, "账户已停用!");
        }else if(!userVO.getPassword().equals(password)) {
            //记录登录错误日志
            po.setUserId(userVO.getId());
            po.setUsername(userVO.getUsername());
            po.setState(0);
            logLoginMapper.insertLog(po);
            return new ResponseBodyVO<>(ResponseBodyVO.Response.CLIENT_FAILURE, "用户名或密码错误!");
        }*/
        //3 根据仿真平台用户名密码颁发仿真平台 token，返回给前端
        String simulationTokenUrl = oauthParameter.getSimulationTokenUri() +
                "?grant_type=password" +
                "&client_id=" + oauthParameter.getSimulationClientId() +
                "&client_secret=" + oauthParameter.getSimulationClientSecret() +
                "&username=" + username +
                "&password=" + password;
        String simulationToken = HttpUtil.get(simulationTokenUrl);
        SimulationTokenVO simulationTokenVO = JsonUtil.jsonToBean(simulationToken, SimulationTokenVO.class);
        return new ResponseBodyVO<>(ResponseBodyVO.Response.SUCCESS, simulationTokenVO);
    }


    /**
     * 令牌刷新
     *
     * @return token 信息
     */
    @RequestMapping("/refreshToken")
    @ResponseBody
    public ResponseBodyVO<SimulationTokenVO> refreshToken(@RequestBody @Validated SignRefreshParameter signRefreshParameter) {
        String refreshToken = signRefreshParameter.getRefreshToken();
        //刷新令牌
        String simulationTokenUrl = oauthParameter.getSimulationTokenUri() +
                "?grant_type=refresh_token" +
                "&client_id=" + oauthParameter.getSimulationClientId() +
                "&client_secret=" + oauthParameter.getSimulationClientSecret() +
                "&refresh_token=" + refreshToken;
        try {
            String simulationToken = HttpUtil.get(simulationTokenUrl);
            SimulationTokenVO simulationTokenVO = JsonUtil.jsonToBean(simulationToken, SimulationTokenVO.class);
            return new ResponseBodyVO<>(ResponseBodyVO.Response.SUCCESS, simulationTokenVO);
        } catch (Exception e) {
            e.printStackTrace();
            return new ResponseBodyVO<>(ResponseBodyVO.Response.CLIENT_FAILURE, "Invalid refresh token");
        }

    }

    /**
     * 服务监控
     */
    @RequestMapping("online")
    @ResponseBody
    public ResponseBodyVO<Integer> online() {
        int online;

        Set<String> keys = redisTemplate.keys("access:*");
        if (CollectionUtil.isEmpty(keys)) {
            online = 0;
        } else {
            online = keys.size();
        }
        return new ResponseBodyVO<>(ResponseBodyVO.Response.SUCCESS, online);
    }

}
