package io.hmit.modules.security.controller;

import io.hmit.common.exception.ErrorCode;
import io.hmit.common.exception.HmitException;
import io.hmit.common.redis.RedisKeys;
import io.hmit.common.redis.RedisUtils;
import io.hmit.common.utils.DateUtils;
import io.hmit.common.utils.IpUtils;
import io.hmit.common.utils.NumberUtils;
import io.hmit.common.utils.Result;
import io.hmit.common.validator.AESUtils;
import io.hmit.common.validator.AssertUtils;
import io.hmit.common.validator.CodeUtils;
import io.hmit.common.validator.ValidatorUtils;
import io.hmit.modules.log.entity.SysLogLoginEntity;
import io.hmit.modules.log.enums.LoginOperationEnum;
import io.hmit.modules.log.enums.LoginStatusEnum;
import io.hmit.modules.log.service.SysLogLoginService;
import io.hmit.modules.security.dto.LoginDTO;
import io.hmit.modules.security.password.PasswordUtils;
import io.hmit.modules.security.service.CaptchaService;
import io.hmit.modules.security.service.SysUserTokenService;
import io.hmit.modules.security.user.SecurityUser;
import io.hmit.modules.security.user.UserDetail;
import io.hmit.modules.sys.dto.SysUserDTO;
import io.hmit.modules.sys.enums.UserStatusEnum;
import io.hmit.modules.sys.service.SysUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Date;

/**
 * 登录
 *
 * @author zsh 408538940@qq.com
 */
@RestController
@Api(tags = "登录管理")
public class LoginController {

    private static final int LOGIN_IP_LIMIT_TIME = 3;          // 登录失败三次，需要进行验证码验证
    private static final int LOGIN_USERNAME_DISABLE_TIME = 10; // 同一个用户名登录失败10次，账号将被冻结
    private static final int LOGIN_USERNAME_PROMPT_TIME = 5;   // 同一个账号错误5次，提醒用户超过10次，账号将被冻结
    private static final long LOGIN_EXPIRE_TIME = 1800;        // 登录失败记录时长
    private static final long LOGIN_CRYPTO_KEY_EXPIRE_TIME = 60 * 60 * 24;        // 登录失败记录时长


    @Autowired
    private SysUserService sysUserService;
    @Autowired
    private SysUserTokenService sysUserTokenService;
    @Autowired
    private CaptchaService captchaService;
    @Autowired
    private SysLogLoginService sysLogLoginService;

    @Autowired
    private RedisUtils redisUtils;

    @GetMapping("captcha")
    @ApiOperation(value = "验证码", produces = "application/octet-stream")
    @ApiImplicitParam(paramType = "query", dataType = "string", name = "uuid", required = true)
    public void captcha(HttpServletResponse response, String uuid) throws IOException {

        //uuid不能为空
        AssertUtils.isBlank(uuid, ErrorCode.IDENTIFIER_NOT_NULL);

        //生成图片验证码
        BufferedImage image = captchaService.create(uuid);

        response.setHeader("Cache-Control", "no-store, no-cache");
        response.setContentType("image/jpeg");
        ServletOutputStream out = response.getOutputStream();
        ImageIO.write(image, "jpg", out);
        out.close();
    }

    @ApiOperation(value = "获取加密的KEY")
    @GetMapping("getCryptoKey")
    public Result getCryptoKey() {
        String key = CodeUtils.getNumCode(16);
        redisUtils.set(RedisKeys.getCryptoKey(key), key, LOGIN_CRYPTO_KEY_EXPIRE_TIME);
        return new Result().ok(AESUtils.encrypt(key));
    }


    @PostMapping("login")
    @ApiOperation(value = "登录")
    public Result login(HttpServletRequest request, @RequestBody LoginDTO login)throws Exception {

        //校验数据
        ValidatorUtils.validateEntity(login);

        //验证码是否正确
        boolean flag = captchaService.validate(login.getUuid(), login.getCaptcha());
        if (!flag) {
            return new Result().error(ErrorCode.CAPTCHA_ERROR);
        }

        //获取解密后的登录名和密码
        checkLogin(request,login);
        String username = login.getUsername();
        String password = login.getPassword();

        //用户信息
//        SysUserDTO user = sysUserService.getByUsername(login.getUsername());
        SysUserDTO user = sysUserService.getByUsername(username);

        SysLogLoginEntity log = new SysLogLoginEntity();
        log.setOperation(LoginOperationEnum.LOGIN.value());
        log.setCreateDate(new Date());
        log.setIp(IpUtils.getIpAddr(request));
        log.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
        log.setIp(IpUtils.getIpAddr(request));

        //用户不存在
        if (user == null) {
            log.setStatus(LoginStatusEnum.FAIL.value());
            log.setCreatorName(login.getUsername());
            sysLogLoginService.save(log);

            throw new HmitException(ErrorCode.ACCOUNT_PASSWORD_ERROR);
        }

        //密码错误
        if (!PasswordUtils.matches(password, user.getPassword())) {
            log.setStatus(LoginStatusEnum.FAIL.value());
            log.setCreator(user.getId());
            log.setCreatorName(user.getUsername());
            sysLogLoginService.save(log);

            addFailureLogin(login, request);
            throw new HmitException(ErrorCode.ACCOUNT_PASSWORD_ERROR);
        }

        //账号停用
        if (user.getStatus() == UserStatusEnum.DISABLE.value()) {
            log.setStatus(LoginStatusEnum.LOCK.value());
            log.setCreator(user.getId());
            log.setCreatorName(user.getUsername());
            sysLogLoginService.save(log);

            throw new HmitException(ErrorCode.ACCOUNT_DISABLE);
        }

        //登录成功
        log.setStatus(LoginStatusEnum.SUCCESS.value());
        log.setCreator(user.getId());
        log.setCreatorName(user.getUsername());
        sysLogLoginService.save(log);

        return sysUserTokenService.createToken(user.getId());
    }

    @PostMapping("logout")
    @ApiOperation(value = "退出")
    public Result logout(HttpServletRequest request) {

        UserDetail user = SecurityUser.getUser();

        //退出
        sysUserTokenService.logout(user.getId());

        //用户信息
        SysLogLoginEntity log = new SysLogLoginEntity();
        log.setOperation(LoginOperationEnum.LOGOUT.value());
        log.setIp(IpUtils.getIpAddr(request));
        log.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT));
        log.setIp(IpUtils.getIpAddr(request));
        log.setStatus(LoginStatusEnum.SUCCESS.value());
        log.setCreator(user.getId());
        log.setCreatorName(user.getUsername());
        log.setCreateDate(new Date());
        sysLogLoginService.save(log);

        return new Result();
    }

    private void checkLogin(HttpServletRequest request, LoginDTO login) throws Exception {
        // 检查cryptoKey有效性
        String cryptoKey = AESUtils.decrypt(login.getCryptoKey());
        if (null == redisUtils.get(RedisKeys.getCryptoKey(cryptoKey))) {
            throw new HmitException("刷新页面重新登陆");
        }

        login.setUsername(AESUtils.decrypt(login.getUsername(), cryptoKey));
        login.setPassword(AESUtils.decrypt(login.getPassword(), cryptoKey));
        checkUsernameLogin(request, login, true);

    }

    private void checkUsernameLogin(HttpServletRequest request, LoginDTO login, boolean prompt) throws Exception {
        int unCount = NumberUtils.initInt(redisUtils.get(RedisKeys.getUserNameKey(login.getUsername())));
        if (unCount >= LOGIN_USERNAME_DISABLE_TIME) {
            throw new HmitException("用户名登录次数超过" + LOGIN_USERNAME_DISABLE_TIME + "次，被锁定，解冻剩余时间 "
                    + DateUtils.formatLong(redisUtils.getExpire(RedisKeys.getUserNameKey(login.getUsername()))));
        }

        if (prompt && unCount >= LOGIN_USERNAME_PROMPT_TIME) {
            throw new HmitException("用户名或密码不正确, 失败登录" + unCount + "次, 超过" + LOGIN_USERNAME_DISABLE_TIME + "次, 账号将被冻结" + LOGIN_EXPIRE_TIME/60 + "分钟");
        }
    }
    private void addFailureLogin(LoginDTO login, HttpServletRequest request) throws Exception {
        redisUtils.incr(RedisKeys.getUserNameKey(login.getUsername()), LOGIN_EXPIRE_TIME);
    }

}
