package com.sy.coinage.sys.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.sy.coinage.sys.model.Dept;
import com.sy.coinage.sys.model.Tenant;
import com.sy.coinage.sys.model.UserExt;
import com.sy.coinage.sys.req.LoginByCardReq;
import com.sy.coinage.sys.service.IDeptService;
import com.sy.coinage.sys.service.ITenantService;
import com.sy.coinage.sys.service.IUserExtService;
import com.sy.coinage.sys.service.IUserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

import java.io.IOException;
import java.util.Date;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.fastjson2.JSON;
import com.sy.coinage.config.CustomConfig;
import com.sy.coinage.core.annotation.CurrentUser;
import com.sy.coinage.core.annotation.LoginNotRequired;
import com.sy.coinage.core.constants.CacheKey;
import com.sy.coinage.core.constants.Constants;
import com.sy.coinage.core.constants.ResponseEnum;
import com.sy.coinage.core.exception.RetException;
import com.sy.coinage.core.req.LoginReq;
import com.sy.coinage.core.util.DateUtils;
import com.sy.coinage.core.util.GenerateTGT;
import com.sy.coinage.core.util.MD5Util;
import com.sy.coinage.core.util.RandImageUtil;
import com.sy.coinage.core.util.RandomUtil;
import com.sy.coinage.core.vo.CacheUserVO;
import com.sy.coinage.core.vo.TokenVO;
import com.sy.coinage.log.model.LoginLog;
import com.sy.coinage.log.service.ILoginLogService;
import com.sy.coinage.core.aop.util.RequestUtil;
import com.sy.coinage.core.redis.RedisService;
import com.sy.coinage.sys.model.User;
import com.sy.coinage.sys.req.CheckCaptchaReq;
import com.sy.coinage.sys.vo.UserMenusVO;

/**
 * 登录、菜单Controller
 *
 * @author lug 2017年12月12日
 */
@Api(value = "登录、用户菜单接口", tags = "010_登录、用户菜单")
@RestController
public class LoginController {

    @Autowired
    private RedisService redisService;

    @Autowired
    private IUserService userService;

    @Autowired
    private IUserExtService userExtService;

    @Autowired
    private ILoginLogService loginLogService;

    @Autowired
    private CustomConfig customConfig;

    @Autowired
    private IDeptService deptService;

    @Autowired
    private ITenantService trenantService;

    /**
     * 租户管理员-登录
     *
     * @param req
     * @param request
     * @return
     */
    @LoginNotRequired
    @ApiOperation(value = "租户管理员登录")
    @PostMapping("/admin/login")
    public TokenVO adminLogin(@RequestBody @Valid LoginReq req, HttpServletRequest request) {
        this.validErrorNum(req.getUsername());
        this.checkLoginCaptcha(req);
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername, req.getUsername()).eq(User::getDelFlag, Constants.STATUS_USABLE)
                .isNull(User::getTenantId);
        User user = this.userService.getOne(queryWrapper);
        return this.baseLogin(user, req, request);
    }

    /**
     * 登录
     *
     * @param req {@link LoginReq}
     * @param request {@link HttpServletRequest}
     * @return Object {@link TokenVO}
     */
    @LoginNotRequired
    @ApiOperation(value = "用户登录")
    @PostMapping("/login")
    public TokenVO login(@RequestBody @Valid LoginReq req, HttpServletRequest request) {
        this.validErrorNum(req.getUsername());
        this.checkLoginCaptcha(req);
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(User::getUsername, req.getUsername()).eq(User::getDelFlag, Constants.STATUS_USABLE)
                .gt(User::getType, 1);
        User user = this.userService.getOne(queryWrapper);
        return this.baseLogin(user, req, request);
    }

    @LoginNotRequired
    @ApiOperation(value = "用户根据卡号登录")
    @PostMapping("/login/byCard")
    public TokenVO loginByCard(@RequestBody @Valid LoginByCardReq req, HttpServletRequest request) {
        LambdaQueryWrapper<UserExt> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserExt::getCardNumber, req.getCard());
        UserExt userExt = this.userExtService.getOne(queryWrapper);
        if (userExt == null) {
            // 用户名错误
            throw new RetException("用户卡号不存在");
        }
        User user = this.userService.getById(userExt.getId());
        if (user.getDelFlag() != Constants.STATUS_USABLE) {
            // 用户名错误
            throw new RetException("用户不存在");
        }
        this.validErrorNum(user.getUsername());
        return this.baseLogin(user, null, request);
    }

    private TokenVO baseLogin(User user, LoginReq req, HttpServletRequest request) {
        if (null == user) {
            // 用户名错误
            throw new RetException(ResponseEnum.USERNAME_ERROR);
        }
        Dept dept = null;
        if (user.getDeptId() != null && user.getDeptId() > 0) {
            dept = deptService.getById(user.getDeptId());
            if (dept.getStatus() == 2) {
                throw new RetException("部门已禁用,请联系管理员");
            }
        }
        if (req != null) {
            // 密码加盐加密
            String pwd = MD5Util.MD5(req.getPassword() + user.getSalt());
            // 密码校验
            if (!user.getPassword().equals(pwd)) {
                long num = this.addErrorNum(req.getUsername());
                if (num > 0) {
                    throw new RetException(
                            "密码错误,剩余" + (customConfig.getLoginErrorNum() - num) + "次");
                }
                // 密码错误
                throw new RetException(ResponseEnum.PWD_ERROR);
            }
        }
        // 登录成功删除错误次数
        this.delErrorNum(user.getUsername());
        if (user.getStatus().intValue() != Constants.STATUS_USABLE) {
            // 帐号禁用
            throw new RetException(ResponseEnum.STATUS_ERROR);
        }
        // 设置用户多地登录
        this.LoginMore(user);
        // 缓存用户 构造响应
        TokenVO data = this.cacheUser(user, request, dept);
        if (user.getTenantId() != null && user.getTenantId() > 0) {
            Tenant tenant = this.trenantService.getById(user.getTenantId());
            data.setTenantName(tenant.getName());
        }
        return data;
    }

    /**
     * 当前用户菜单树
     *
     * @param user
     * @return
     */
    @ApiOperation(value = "当前用户菜单树")
    @GetMapping(value = "/menusTree")
    public List<UserMenusVO> menusTree(@CurrentUser CacheUserVO user) {
        if (null != user.getTenantId() && user.getTenantId() > 0) {
            List<UserMenusVO> data = this.userService.menusTree(user.getId());
            return data;
        }
        return null;
    }

    /**
     * 登出
     *
     * @param user {@link CacheUserVO}
     * @param request {@link HttpServletRequest}
     * @return
     */
    @ApiOperation(value = "登出")
    @GetMapping(value = "/logout")
    public Object logout(@CurrentUser CacheUserVO user, HttpServletRequest request) {
        // 删除缓存用户
        String key = CacheKey.TOKEN + user.getToken();
        this.redisService.del(key);
        // 添加登出记录
        this.addLoginLog(user, Constants.STATUS_FORBIDDEN, request);
        return "";
    }

    /**
     * 后台生成图形验证码
     *
     * @param key
     */
    @LoginNotRequired
    @GetMapping(value = "/randomImage/{key}")
    public Object randomImage(@PathVariable String key) {
        String code = RandomUtil.randomStr(4);
        String lowerCaseCode = code.toLowerCase();
        String realKey = MD5Util.MD5(lowerCaseCode + key);
        this.redisService.set(realKey, lowerCaseCode);
        this.redisService.expire(realKey, 120);
        String data = null;
        try {
            data = RandImageUtil.generate(code);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return data;
    }

    /**
     * 图形码验证
     *
     * @param {@link CheckCaptchaReq}
     * @return
     */
    @LoginNotRequired
    @GetMapping(value = "/checkCaptcha")
    public Object checkCaptcha(@RequestBody @Valid CheckCaptchaReq req) {
        String realKey = MD5Util.MD5(req.getCaptcha().toLowerCase() + req.getCheckKey());
        String checkCode = this.redisService.get(realKey);
        if (null == checkCode || !checkCode.equals(req.getCaptcha().toLowerCase())) {
            throw new RetException("验证码错误");
        }
        return "";
    }

    /**
     * 记录用户登录密码错误次数
     *
     * @param username
     */
    private long addErrorNum(String username) {
        if (customConfig.getLoginErrorNum() > 0) {
            String date = DateUtils.getTodayBegin();
            String key = CacheKey.LOGIN_ERROR_NUM + date + ":" + username;
            long num = redisService.incr(key, CacheKey.EXPIRED_ONE_DAY);
            return num;
        }
        return 0;
    }

    /**
     * 删除用户登录密码错误次数
     *
     * @param username
     */
    private void delErrorNum(String username) {
        if (customConfig.getLoginErrorNum() > 0) {
            String date = DateUtils.getTodayBegin();
            String key = CacheKey.LOGIN_ERROR_NUM + date + ":" + username;
            redisService.del(key);
        }
    }

    /**
     * 验证用户登录密码错误次数
     *
     * @param username
     */
    private void validErrorNum(String username) {
        if (customConfig.getLoginErrorNum() > 0) {
            String date = DateUtils.getTodayBegin();
            String key = CacheKey.LOGIN_ERROR_NUM + date + ":" + username;
            Integer num = (Integer) redisService.getObject(key);
            if (null != num && num >= customConfig.getLoginErrorNum()) {
                throw new RetException("密码输入错误次数过多，请明天再试");
            }
        }
    }

    /**
     * 登录验证图形码
     *
     * @param req
     */
    private void checkLoginCaptcha(@Valid LoginReq req) {
        String captcha = req.getCaptcha();
        if (customConfig.getCaptcha()) {
            if (StringUtils.isBlank(captcha)) {
                throw new RetException("验证码错误");
            }
            if (StringUtils.isBlank(req.getCheckKey())) {
                throw new RetException("验证密钥错误");
            }
            String lowerCaseCaptcha = captcha.toLowerCase();
            String realKey = MD5Util.MD5(lowerCaseCaptcha + req.getCheckKey());
            String checkCode = this.redisService.get(realKey);
            if (null == checkCode || !checkCode.toString().equals(lowerCaseCaptcha)) {
                throw new RetException("验证码错误");
            }
        }
    }

    /**
     * 添加登录登出记录
     *
     * @param user {@link LoginReq}
     * @param type 1登入 2登出
     * @param request {@link HttpServletRequest}
     */
    private void addLoginLog(CacheUserVO user, int type, HttpServletRequest request) {
        if (customConfig.getLoginLog()) {
            LoginLog model = new LoginLog();
            model.setIp(user.getIp());
            model.setRealName(user.getRealName());
            model.setType(type);
            model.setUserId(user.getId());
            model.setUsername(user.getUsername());
            model.setTenantId(user.getTenantId());
            this.loginLogService.save(model);
        }
    }

    /**
     * 缓存用户 构造响应
     *
     * @param user
     * @param request
     * @return
     */
    private TokenVO cacheUser(User user, HttpServletRequest request, Dept dept) {
        // 登录令牌
        String token = GenerateTGT.generateToken();
        // 添加缓存用户
        CacheUserVO cacheUser = new CacheUserVO();
        BeanUtils.copyProperties(user, cacheUser);
        if (dept != null) {
            cacheUser.setDept(dept.getName());
            if (StringUtils.isNotBlank(dept.getRemark())) {
                cacheUser.setSelfWareId(Integer.parseInt(dept.getRemark()));
            }
        }
        cacheUser.setToken(token);
        String ip = null;
        try {
            ip = RequestUtil.getRequestIp(request);
        } catch (Exception e) {
            e.printStackTrace();
        }
        cacheUser.setIp(ip);
        if (customConfig.getCheckPermission() && user.getTenantId() != null) { // 角色id及按钮操作权限
            List<Integer> roleIds = this.userService.listRoleIds(user.getId());
            cacheUser.setRoleIds(roleIds);
            List<String> permissionList = this.userService.getPermission(roleIds);
            if (!permissionList.isEmpty()) {
                cacheUser.setPermissionList(permissionList);
            }
        }
        String key = CacheKey.TOKEN + token;
        redisService.set(key, JSON.toJSONString(cacheUser));
        redisService.expire(key, CacheKey.EXPIRED_TWO_HOUR);
        if (!customConfig.getLoginMore()) {
            String userKey = CacheKey.USER_ID + user.getId();
            redisService.set(userKey, token);
            redisService.expire(userKey, CacheKey.EXPIRED_TWO_HOUR);
        }
        // 构造返回对象 真实姓名、安全令牌、菜单树
        TokenVO data = new TokenVO();
        BeanUtils.copyProperties(cacheUser, data);
        // 是否登录返回菜单树
        if (customConfig.getLoginMenu()) {
            data.setMenus(this.menusTree(cacheUser));
        }
        // 添加登录扩展信息
        this.addLoginExt(cacheUser);
        // 添加登录记录
        this.addLoginLog(cacheUser, Constants.STATUS_USABLE, request);
        return data;
    }

    // 添加登录扩展信息
    private void addLoginExt(CacheUserVO cacheUser) {
        if (customConfig.getLoginExt()) {
            UserExt userExt = this.userExtService.getById(cacheUser.getId());
            if (userExt == null) {
                userExt = new UserExt();
                userExt.setId(cacheUser.getId());
                userExt.setLoginNum(1);
                userExt.setLastTime(new Date());
                userExt.setIp(cacheUser.getIp());
                this.userExtService.save(userExt);
            } else {
                userExt.setLoginNum(userExt.getLoginNum() + 1);
                userExt.setLastTime(new Date());
                userExt.setIp(cacheUser.getIp());
                this.userExtService.updateById(userExt);
            }
        }
    }

    /**
     * 设置用户多地登录
     *
     * @param user
     */
    private void LoginMore(User user) {
        if (!customConfig.getLoginMore()) {
            String key = CacheKey.USER_ID + user.getId();
            // 已经登录的用户删除token
            if (redisService.hasKey(key)) {
                redisService.del(CacheKey.TOKEN + redisService.get(key));
            }
        }
    }

}
