package com.ktg.framework.web.service;

import cn.hutool.core.lang.Assert;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ktg.common.constant.Constants;
import com.ktg.common.core.domain.AjaxResult;
import com.ktg.common.core.domain.entity.SysUser;
import com.ktg.common.core.domain.model.FingerprintLoginBody;
import com.ktg.common.core.domain.model.LoginBody;
import com.ktg.common.core.domain.model.LoginUser;
import com.ktg.common.core.redis.RedisCache;
import com.ktg.common.exception.ServiceException;
import com.ktg.common.exception.user.CaptchaException;
import com.ktg.common.exception.user.CaptchaExpireException;
import com.ktg.common.exception.user.UserPasswordNotMatchException;
import com.ktg.common.utils.*;
import com.ktg.common.utils.face.ArcSoftMothodUtil;
import com.ktg.common.utils.face.FaceMatchUtil;
import com.ktg.common.utils.ip.IpUtils;
import com.ktg.common.vo.FaceMatchVO;
import com.ktg.common.vo.VerificationVO;
import com.ktg.framework.manager.AsyncManager;
import com.ktg.framework.manager.factory.AsyncFactory;
import com.ktg.system.domain.SysUserCharacteristic;
import com.ktg.system.service.ISysConfigService;
import com.ktg.system.service.ISysUserCharacteristicService;
import com.ktg.system.service.ISysUserOnlineService;
import com.ktg.system.service.ISysUserService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 登录校验方法
 *
 * @author guoruan
 */
@Slf4j
@Component
public class SysLoginService {
    @Autowired
    private TokenService tokenService;
    @Resource
    private AuthenticationManager authenticationManager;
    @Autowired
    private RedisCache redisCache;
    @Autowired
    private ISysUserService userService;
    @Autowired
    private ISysConfigService configService;
    @Autowired
    private ISysUserOnlineService userOnlineService;
    @Resource
    private SysPermissionService permissionService;
    // 令牌秘钥
    @Value("${token.secret}")
    private String secret;
    @Autowired
    private ISysUserService iSysUserService;
    @Autowired
    private ISysUserCharacteristicService iSysUserCharacteristicService;


    /**
     * 登录验证
     *
     * @param username 用户名
     * @param password 密码
     * @param code     验证码
     * @param uuid     唯一标识
     * @return 结果
     */
    public String login(String username, String password, String code, String uuid) {
        boolean captchaOnOff = configService.selectCaptchaOnOff();
        // 验证码开关
        if (captchaOnOff) {
            validateCaptcha(username, code, uuid);
        }
        // 用户验证
        Authentication authentication = null;
        try {
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager
                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
        } catch (Exception e) {
            if (e instanceof BadCredentialsException) {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            } else {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                throw new ServiceException(e.getMessage());
            }
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        recordLoginInfo(loginUser.getUserId());
        // 生成token
        String token = tokenService.createToken(loginUser);
        // 清理多余的登录者
        // userOnlineService.forceLogoutByName(username);
        return token;
    }
    @Autowired
    private UserDetailsService userDetailsService;

    public String loginWithoutPassword(SysUser sysUser) {
        // 用户验证
        Authentication authentication;
        try {
            //直接不用springsecurity 认证、自己构造出数据
            UserDetails userDetails = new LoginUser(sysUser.getUserId(), sysUser.getDeptId(), sysUser, permissionService.getMenuPermission(sysUser));
            authentication = new UsernamePasswordAuthenticationToken(userDetails, null,
                    AuthorityUtils.createAuthorityList("ROLE_USER"));
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } catch (Exception e) {
            if (e instanceof BadCredentialsException) {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            } else {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_FAIL, e.getMessage()));
                throw new ServiceException(e.getMessage());
            }
        } finally {
            SecurityContextHolder.clearContext();
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(sysUser.getUserName(), Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        // 校验登陆账号
        userDetailsService.loadUserByUsername(loginUser.getUsername());
        recordLoginInfo(loginUser.getUserId());
        // 生成token
        return tokenService.createToken(loginUser);
    }

    /**
     * 校验验证码
     *
     * @param username 用户名
     * @param code     验证码
     * @param uuid     唯一标识
     * @return 结果
     */
    public void validateCaptcha(String username, String code, String uuid) {
        String verifyKey = Constants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
        String captcha = redisCache.getCacheObject(verifyKey);
        redisCache.deleteObject(verifyKey);
        if (captcha == null) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
            throw new CaptchaExpireException();
        }
        if (!code.equalsIgnoreCase(captcha)) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
            throw new CaptchaException();
        }
    }

    /**
     * 记录登录信息
     *
     * @param userId 用户ID
     */
    public void recordLoginInfo(Long userId) {
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
        sysUser.setLoginDate(DateUtils.getNowDate());
        userService.updateUserProfile(sysUser);
    }

    public String machineLogin(LoginBody loginBody) {
        // 生成令牌
        String token = login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
                loginBody.getUuid());

        // 因为只能同时在线一个，清理之前的登录
        String oldToken = redisCache.getCacheObject(Constants.CABINET_LOGIN_TOKEN_KEY + loginBody.getUsername());
        // 删除之前的登陆信息
        if (StringUtils.isNotEmpty(oldToken)) {
            try {
                Claims claims = Jwts.parser()
                        .setSigningKey(secret)
                        .parseClaimsJws(oldToken)
                        .getBody();
                // 解析对应的权限以及用户信息
                String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
                redisCache.deleteObject(Constants.LOGIN_TOKEN_KEY + uuid);
            } catch (Exception ignored) {
            }
        }
        // 更换上次登录信息的记录
        redisCache.setCacheObject(Constants.CABINET_LOGIN_TOKEN_KEY + loginBody.getUsername(), token);
        return token;
    }

    public AjaxResult loginByFingerprint(FingerprintLoginBody loginBody) {
        Assert.notBlank(loginBody.getInputImg(), "输入的指纹不能为空！");
        // 获取所有的用户指纹
        List<SysUserCharacteristic> list = iSysUserCharacteristicService.list(Wrappers.<SysUserCharacteristic>lambdaQuery()
                .eq(SysUserCharacteristic::getType, "0"));
        Assert.isFalse(list.isEmpty(), "指纹库中暂无您的指纹信息！");
        List<String> collect = list.stream().map(SysUserCharacteristic::getContent).collect(Collectors.toList());
        // 通过指纹获取最相似的用户
        VerificationVO verificationVO = FingerprintComparisonByImg.completableFutureComparison(loginBody.getInputImg(), new HashSet<>(collect));
        Assert.notNull(verificationVO, "无法根据指纹确定您的身份，请通过其它方式登录！");
        String fingerprintImg = verificationVO.getFingerprintImg();
        SysUserCharacteristic one = iSysUserCharacteristicService.getOne(Wrappers.<SysUserCharacteristic>lambdaQuery()
                .eq(SysUserCharacteristic::getContent, fingerprintImg));
        SysUser sysUser = iSysUserService.getById(one.getUserId());
        // 生成令牌
        AjaxResult ajax = AjaxResult.success();
        String token = loginWithoutPassword(sysUser);
        ajax.put(Constants.TOKEN, token);
        ajax.put("nickName", sysUser.getNickName());
        return ajax;
    }

    public AjaxResult loginByFingerprintDat(MultipartFile file) throws IOException {
        Assert.isTrue(file.getSize() > 0, "输入的指纹不能为空！");
        // 获取所有的用户指纹
        List<SysUserCharacteristic> list = iSysUserCharacteristicService.list(Wrappers.<SysUserCharacteristic>lambdaQuery()
                .eq(SysUserCharacteristic::getType, "1"));
        Assert.isFalse(list.isEmpty(), "指纹库中暂无您的指纹信息！");
        List<String> collect = list.stream().map(SysUserCharacteristic::getContent).collect(Collectors.toList());
        // 通过指纹获取最相似的用户
        VerificationVO verificationVO = FingerprintComparisonByDat.completableFutureComparison(file, new HashSet<>(collect));
        Assert.notNull(verificationVO, "无法根据指纹确定您的身份，请通过其它方式登录！");
        String fingerprint = verificationVO.getFingerprint();
        SysUserCharacteristic one = iSysUserCharacteristicService.getOne(Wrappers.<SysUserCharacteristic>lambdaQuery()
                .eq(SysUserCharacteristic::getContent, fingerprint)
                .orderByDesc(SysUserCharacteristic::getRecordId)
                .last("offset 0 rows fetch next 1 rows only"));
        SysUser sysUser1 = iSysUserService.getById(one.getUserId());
        SysUser sysUser = userService.selectUserByUserName(sysUser1.getUserName());
        // 生成令牌
        AjaxResult ajax = AjaxResult.success();
        String token = loginWithoutPassword(sysUser);
        ajax.put(Constants.TOKEN, token);
        ajax.put("nickName", sysUser.getNickName());
        return ajax;
    }

    public AjaxResult loginByFace(MultipartFile file) throws IOException {
        Assert.isTrue(file.getSize() > 0, "输入的人脸不能为空！");
        // 获取所有的用户人脸
        List<SysUserCharacteristic> list = iSysUserCharacteristicService.list(Wrappers.<SysUserCharacteristic>lambdaQuery()
                .eq(SysUserCharacteristic::getType, "2"));
        Assert.isFalse(list.isEmpty(), "人脸库中暂无您的人脸信息！");
        List<String> collect = list.stream().map(SysUserCharacteristic::getContent).collect(Collectors.toList());
        // List<FaceCutVO> bean = BeanUtils.toBean(list, FaceCutVO.class);
        // 通过人脸获取最相似的用户
        // 记录开始时间
        long startTime1 = System.currentTimeMillis();
        // FaceMatchVO faceMatchVO = FaceMatchUtil.faceRecognitionComparison(file, new HashSet<>(collect));
        FaceMatchVO faceMatchVO = FaceMatchUtil.faceRecognitionComparison(file, collect);
        // 记录结束时间
        long endTime1 = System.currentTimeMillis();
        // 计算时间差（毫秒）
        long duration1 = endTime1 - startTime1;
        System.out.println("Execution time in milliseconds: " + duration1);
        Assert.notNull(faceMatchVO, "无法根据人脸确定您的身份，请通过其它方式登录！");
        String okFace = faceMatchVO.getContent();
        SysUserCharacteristic one = iSysUserCharacteristicService.getOne(Wrappers.<SysUserCharacteristic>lambdaQuery()
                .eq(SysUserCharacteristic::getContent, okFace)
                .orderByDesc(SysUserCharacteristic::getRecordId)
                .last("offset 0 rows fetch next 1 rows only"));
        SysUser sysUser1 = iSysUserService.getById(one.getUserId());
        SysUser sysUser = userService.selectUserByUserName(sysUser1.getUserName());
        // 生成令牌
        AjaxResult ajax = AjaxResult.success();
        String token = loginWithoutPassword(sysUser);
        ajax.put(Constants.TOKEN, token);
        ajax.put("nickName", sysUser.getNickName());
        return ajax;
    }

    public AjaxResult loginByArcFace(MultipartFile file) throws IOException {

        Assert.isTrue(file.getSize() > 0, "输入的人脸不能为空！");
        // 获取所有的用户人脸
        List<SysUserCharacteristic> list = iSysUserCharacteristicService.list(Wrappers.<SysUserCharacteristic>lambdaQuery()
                .eq(SysUserCharacteristic::getType, "2"));
        Assert.isFalse(list.isEmpty(), "人脸库中暂无您的人脸信息！");
        List<String> collect = list.stream().map(SysUserCharacteristic::getContent).collect(Collectors.toList());
        // 通过人脸获取最相似的用户
        // FaceMatchVO faceMatchVO = ArcSoftMothodUtil.completableFutureComparison(file, collect);
        FaceMatchVO faceMatchVO = ArcSoftMothodUtil.completableFutureComparison(file, new HashSet<>(collect));
        Assert.notNull(faceMatchVO, "无法根据人脸确定您的身份，请通过其它方式登录！");
        String okFace = faceMatchVO.getContent();
        SysUserCharacteristic one = iSysUserCharacteristicService.getOne(Wrappers.<SysUserCharacteristic>lambdaQuery()
                .eq(SysUserCharacteristic::getContent, okFace)
                .orderByDesc(SysUserCharacteristic::getRecordId)
                .last("offset 0 rows fetch next 1 rows only"));
        SysUser sysUser = iSysUserService.getById(one.getUserId());
        // 生成令牌
        AjaxResult ajax = AjaxResult.success();
        String token = loginWithoutPassword(sysUser);
        ajax.put(Constants.TOKEN, token);
        ajax.put("nickName", sysUser.getNickName());
        return ajax;
    }


}
