package com.gci.boot.modules.system.controller;

import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.gci.boot.common.log.annotation.AddSysLog;
import com.gci.boot.common.util.CacheUtil;
import com.gci.boot.common.util.LimitCheckUtil;
import com.gci.boot.constant.CacheConstant;
import com.gci.boot.constant.CommonConstant;
import com.gci.boot.common.util.MD5Util;
import com.gci.boot.exception.GciBootException;
import com.gci.boot.modules.system.entity.LoginTimesValidate;
import com.gci.boot.modules.system.service.LoginTimesValidateService;
import com.gci.boot.modules.system.vo.ApiTokenModel;
import com.gci.boot.modules.system.vo.LimitRequest;
import com.gci.boot.modules.system.vo.LoginUser;
import com.gci.boot.properties.GciSecurityProperties;
import com.gci.boot.modules.system.util.PasswordUtil;
import com.gci.boot.common.util.RedisUtil;
import com.gci.boot.modules.system.util.AesEncryptUtil;
import com.gci.boot.common.vo.Result;
import com.gci.boot.modules.system.entity.SysUser;
import com.gci.boot.modules.system.vo.SysLoginModel;
import com.gci.boot.modules.system.service.ISysUserService;
import com.gci.boot.modules.system.shiro.authc.JwtToken;
import com.gci.boot.modules.system.shiro.vo.DefContants;
import com.gci.boot.modules.system.util.JwtUtil;
import com.gci.boot.modules.system.util.RandImageUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/sys")
@Api(tags = "用户登录")
@EnableConfigurationProperties(GciSecurityProperties.class)
@Slf4j
public class LoginController {

    @Autowired
    private ISysUserService sysUserService;

    @Autowired
    private LoginTimesValidateService loginTimesValidateService;

    @Autowired
    private GciSecurityProperties gciSecurityProperties;

    private static final String BASE_CHECK_CODES = "qwertyuiplkjhgfdsazxcvbnmQWERTYUPLKJHGFDSAZXCVBNM1234567890";

    @PostMapping("/getApiToken")
    public Result<?> getApiToken(@RequestBody ApiTokenModel apiTokenModel, HttpServletRequest request) {
        try {
            //检查
            String captcha = apiTokenModel.getAppKey();
            if (captcha == null) {
                return Result.error("密钥无效！");
            }
            //1. 校验用户是否有效
            SysUser user = sysUserService.getByUsername(apiTokenModel.getAppId());
            if(user == null || CommonConstant.USER_FREEZE.toString().equals(user.getStatus())){
                //return Result.error("用户不存在！");
                return Result.error("分配账号或密码错误！");
            }
            String username = user.getUsername();
            //2. 校验用户名或密码是否正确
            LimitCheckUtil.check(request, username,null);
            String userPassword = apiTokenModel.getAppKey();
            String sysPassword = user.getPassword();
            if(!userPassword.equals(sysPassword)){
                return Result.error("分配账号或密钥错误！");
            }
            //3.生成token，存redis
            Subject currentUser = SecurityUtils.getSubject();
            String token = JwtUtil.sign(username, user.getPassword());
            //每天限制指定次数
            LimitCheckUtil.putNeedCheck(username,token);
            // 设置token缓存有效时间
            RedisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token, gciSecurityProperties.getShiro().getExpireTime()/1000);
            currentUser.login(new JwtToken(token));
            Map<String, Object> resultMap = new HashMap<>();
            resultMap.put("token", token);
            return Result.ok(resultMap);
        }catch (GciBootException gciBootException){
            log.error("",gciBootException);
            return Result.error(gciBootException.getMessage());
        }catch (Exception e) {
            log.error("", e);
            return Result.error("获取密钥失败！");
        }
    }


    @AddSysLog(value = "用户登录", logType = 1)
    @PostMapping("/login")
    public Result<?> login(@RequestBody SysLoginModel sysLoginModel, HttpServletRequest request) {
        try {
            //检查验证码
            String captcha = sysLoginModel.getCaptcha();
            if (captcha == null) {
                return Result.error("验证码无效！");
            }
            String lowerCaseCaptcha = captcha.toLowerCase();
            String realKey = MD5Util.MD5Encode(lowerCaseCaptcha + sysLoginModel.getCheckKey(), "utf-8");
            Object checkCode = RedisUtil.get(realKey);
            RedisUtil.del(realKey);
            if (checkCode == null || !checkCode.equals(lowerCaseCaptcha)) {
                return Result.error("验证码错误！");
            }
            //1. 校验用户是否有效
            SysUser user = sysUserService.getByUsername(sysLoginModel.getUsername());
            if(user == null || CommonConstant.USER_FREEZE.toString().equals(user.getStatus())){
                //return Result.error("用户不存在！");
                return Result.error("账号或密码错误！");
            }
            //2. 校验用户名或密码是否正确
            String password = AesEncryptUtil.desEncrypt(sysLoginModel.getPassword().replaceAll("%2B", "\\+")).trim();
            String userpassword = PasswordUtil.encrypt(sysLoginModel.getUsername(), password, user.getSalt());
            String syspassword = user.getPassword();

            LoginTimesValidate validate = loginTimesValidateService.getManageTimes(sysLoginModel.getUsername());
            if (validate.nonTimes()) {
                return Result.error("连续输入错误密码次数超限，请稍后重试");
            }

            if(!userpassword.equals(syspassword)){
                loginTimesValidateService.saveManageTimes(validate);
                return Result.error("账号或密码错误！");
            }
            //3.生成token，存redis
            Subject currentUser = SecurityUtils.getSubject();

            String token = JwtUtil.sign(user.getUsername(), user.getPassword());

            String pcToken = String.valueOf(RedisUtil.get(CommonConstant.PREFIX_PC_USER + user.getUsername()));
            if(ObjectUtil.isNotEmpty(pcToken)){
                RedisUtil.del(CommonConstant.PREFIX_USER_TOKEN + pcToken);
            }
            // 设置token缓存有效时间
            RedisUtil.set(CommonConstant.PREFIX_USER_TOKEN + token, token, gciSecurityProperties.getShiro().getExpireTime()/1000);
            RedisUtil.set(CommonConstant.PREFIX_PC_USER+user.getUsername(),token);

            currentUser.login(new JwtToken(token));

            Map<String, Object> resultMap = new HashMap<>();
            user.setPhone(sysUserService.processPhone(user));
            user.setPassword(null);
            user.setSalt(null);
            user.setEncryptPhone(null);
            user.setSecretPhone(null);

            resultMap.put("userInfo", user);
            resultMap.put("token", token);
            return Result.ok(resultMap);
        } catch (Exception e) {
            log.error("", e);
            return Result.error("登录失败！");
        }
    }

    /**
     * 后台生成图形验证码 ：有效
     *
     * @param key
     */
    @ApiOperation("获取验证码")
    @GetMapping("/randomImage/{key}")
    public Result<String> randomImage(@PathVariable String key) {
        Result<String> res = new Result<String>();
        try {
            String code = RandomUtil.randomString(BASE_CHECK_CODES, 4);
            String lowerCaseCode = code.toLowerCase();
            String realKey = MD5Util.MD5Encode(lowerCaseCode + key, "utf-8");
            RedisUtil.set(realKey, lowerCaseCode, 60);
            String base64 = RandImageUtil.generate(code);
            res.setSuccess(true);
            res.setResult(base64);
        } catch (Exception e) {
            res.error500("获取验证码出错" + e.getMessage());
            e.printStackTrace();
        }
        return res;
    }
    /**
     * 退出登录
     *
     * @param request
     * @return
     */
    @AddSysLog(value = "退出登录", logType = 1)
    @RequestMapping("/logout")
    public Result<?> logout(HttpServletRequest request) {
        //用户退出逻辑
        String token = request.getHeader(DefContants.X_ACCESS_TOKEN);
        if (StringUtils.isBlank(token)) {
            return Result.error("退出登录失败！");
        }
        String username = JwtUtil.getUsername(token);
        SysUser sysUser = sysUserService.getByUsername(username);
        if (sysUser != null) {
            log.info(" 用户名:  " + sysUser.getRealname() + ",退出成功！ ");
            //清空用户登录Token缓存
            RedisUtil.del(CommonConstant.PREFIX_USER_TOKEN + token);
            //调用shiro的logout
            SecurityUtils.getSubject().logout();
            //清空用户的缓存信息（包括部门信息），例如sys:cache:user::<username>
            CacheUtil.evit(CacheConstant.SYS_USERS_CACHE, sysUser.getUsername());
            //清空用户登录Shiro权限缓存
            CacheUtil.evit(CacheConstant.SYS_AUTHORIZATION_CACHE, sysUser.getUsername());
            return Result.ok("退出登录成功！");
        } else {
            return Result.error("Token无效!");
        }
    }

    /**
     * 获取加密字符串
     *
     * @return
     */
    @GetMapping("/getEncryptedString")
    public Result<Map<String, String>> getEncryptedString() {
        Result<Map<String, String>> result = new Result<Map<String, String>>();
        Map<String, String> map = new HashMap<String, String>();
        map.put("key", AesEncryptUtil.key);
        map.put("iv", AesEncryptUtil.iv);
        result.setResult(map);
        return result;
    }


    /**
     * 登陆成功选择用户当前部门
     *
     * @param user
     * @return
     */
    @ApiOperation("切换部门接口")
    @PutMapping("/selectDepart")
    public Result<?> selectDepart(@RequestBody SysUser user) {
        String username = user.getUsername();
        LoginUser loginUser = (LoginUser) SecurityUtils.getSubject().getPrincipal();
        if (StringUtils.isBlank(username)) {
            username = loginUser.getUsername();
        }
        String orgCode = user.getOrgCode();
        loginUser.setOrgCode(user.getOrgCode());
        sysUserService.lambdaUpdate().eq(SysUser::getUsername, username)
                .set(SysUser::getOrgCode, orgCode)
                .update();
        SysUser sysUser = sysUserService.getByUsername(username);
        CacheUtil.evit(CacheConstant.SYS_USERS_CACHE, username);
        Map<String, Object> map = new HashMap<>();
        map.put("userInfo", sysUser);
        return Result.ok(map);
    }
}
