package com.skr.mdm.authentication.user.qr;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.SM2;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.skr.mdm.bean.dto.UserLoginCallbackDTO;
import com.skr.mdm.bean.qo.GaodeTerminalQO;
import com.skr.mdm.dao.AuCompanyDao;
import com.skr.mdm.dao.DeviceEquipmentDao;
import com.skr.mdm.dto.LoginUserDTO;
import com.skr.mdm.entity.AuCompanyDO;
import com.skr.mdm.entity.DeviceEquipmentDO;
import com.skr.mdm.enums.DeleteStateEnum;
import com.skr.mdm.enums.EquipmentTypeEnum;
import com.skr.mdm.rabbitmq.RabbitMQProvider;
import com.skr.mdm.util.RegisterEquipmentUtil;
import com.skr.mdm.util.SmUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 用户名密码登录 扫码登录
 *
 * @author nfq
 * @date 2020/9/16 16:37
 */
@Slf4j
public class UsernameAuthenticationProvider implements AuthenticationProvider {

    private static final String QR_CODE = "QR";

    private UsernameDetailsServiceImpl userDetailsService;
    private DeviceEquipmentDao equipmentDao;
    private PasswordEncoder passwordEncoder;
    private AuCompanyDao companyDao;
    private RabbitMQProvider rabbitMQProvider;

    private static void registerGaodeTerminal(DeviceEquipmentDO equipmentDO, LoginUserDTO loginUserDTO, AuCompanyDO companyDO) {
        if (companyDO.getSid() != null) {
            GaodeTerminalQO gaodeTerminalQO = GaodeTerminalQO.builder()
                    .sid(companyDO.getSid())
                    .phoneNumber(loginUserDTO.getPhoneNumber())
                    .equipmentName(equipmentDO.getEquipmentName())
                    .userName(equipmentDO.getUserName())
                    .departmentName(equipmentDO.getDepartmentName())
                    .companyName(companyDO.getCompanyName())
                    .imei(equipmentDO.getImei())
                    .oaid(equipmentDO.getOaid())
                    .uuid(equipmentDO.getUuid())
                    .build();
            RegisterEquipmentUtil.registerGaodeTerminal(gaodeTerminalQO, equipmentDO);
        }
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        UsernameAuthenticationToken authenticationToken = (UsernameAuthenticationToken) authentication;
        String principal = (String) authenticationToken.getPrincipal();
        Object credentials = authenticationToken.getCredentials();
        String imei = (String) authenticationToken.getImei();
        String oaid = (String) authenticationToken.getOaid();
        String uuid = (String) authenticationToken.getUuid();
        String loginMethod = (String) authenticationToken.getLoginMethod();
        String companyId = (String) authenticationToken.getCompanyId();

        UserDetails userDetails = null;
        String loginType = "";
        // 检查imei oaid UUID是否注册
        DeviceEquipmentDO equipmentDO = null;
        if (StringUtils.isNotBlank(imei)) {
            loginType = "imei:" + imei;
            String endImei = null;
            if (imei.contains("-")) {
                String[] split = StringUtils.split(imei, "-");
                imei = split[0];
                endImei = split[1];
            }
            equipmentDO = new LambdaQueryChainWrapper<>(equipmentDao)
                    .eq(DeviceEquipmentDO::getImei, imei)
                    .eq(DeviceEquipmentDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState())
                    .eq(DeviceEquipmentDO::getCompanyId, Integer.valueOf(companyId))
                    .one();
            if (equipmentDO == null && StringUtils.isNotBlank(endImei)) {
                equipmentDO = new LambdaQueryChainWrapper<>(equipmentDao)
                        .eq(DeviceEquipmentDO::getCompanyId, companyId)
                        .eq(DeviceEquipmentDO::getImei, endImei)
                        .eq(DeviceEquipmentDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState())
                        .one();
            }
        }
        if (Objects.isNull(equipmentDO) && StringUtils.isNotBlank(oaid)) {
            loginType = "oaid:" + oaid;
            equipmentDO = new LambdaQueryChainWrapper<>(equipmentDao)
                    .eq(DeviceEquipmentDO::getOaid, oaid)
                    .eq(DeviceEquipmentDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState())
                    .eq(DeviceEquipmentDO::getCompanyId, Integer.valueOf(companyId))
                    .one();
        }
        if (Objects.isNull(equipmentDO) && StringUtils.isNotBlank(uuid)) {
            loginType = "uuid:" + uuid;
            equipmentDO = new LambdaQueryChainWrapper<>(equipmentDao)
                    .eq(DeviceEquipmentDO::getUuid, uuid)
                    .eq(DeviceEquipmentDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState())
                    .eq(DeviceEquipmentDO::getCompanyId, Integer.valueOf(companyId))
                    .one();
        }
        // imei oaid uuid有一个唯一标识即可
        if (equipmentDO == null) {
            // 校验用户名
            userDetailsService.setCompanyId(Integer.valueOf(companyId));
            userDetails = userDetailsService.loadUserByUsername(principal);
        }

        if (userDetails == null) {
            log.error(loginType + "已存在");
            throw new InternalAuthenticationServiceException(
                    "UserDetailsService returned null, which is an interface contract violation " + loginType + "  already exist!");
        } else {
            // 判断是否二维码登录
            if (!StringUtils.equals(QR_CODE, loginMethod)) {
                // 用户名密码登录校验密码
                boolean matches = passwordEncoder.matches(credentials.toString(), userDetails.getPassword());
                if (!matches) {
                    throw new BadCredentialsException("password not matches");
                }
            }
            // 注册设备
            equipmentDO = new DeviceEquipmentDO();
            LoginUserDTO loginUserDTO = (LoginUserDTO) userDetails;
            equipmentDO.setUserId(loginUserDTO.getUserId());
            equipmentDO.setUserName(loginUserDTO.getUserName());
            equipmentDO.setDepartmentId(loginUserDTO.getDepartmentId());
            equipmentDO.setDepartmentName(loginUserDTO.getDepartmentName());
            equipmentDO.setCompanyId(loginUserDTO.getCompanyId());
            equipmentDO.setEquipmentType(EquipmentTypeEnum.CELL_PHONE.getType());
            equipmentDO.setImei(imei);
            equipmentDO.setOaid(oaid);
            equipmentDO.setUuid(uuid);
            equipmentDO.setActivationTime(new Date());
            String equipmentName = getEquipmentName(loginUserDTO.getUserId(), equipmentDO.getUserName(), Integer.valueOf(companyId));
            equipmentDO.setEquipmentName(equipmentName);
            AuCompanyDO companyDO = companyDao.selectById(loginUserDTO.getCompanyId());
            // 检查设备数量限制
            checkLicenseLimit(loginUserDTO, companyDO);
            // 注册猎鹰终端
            registerGaodeTerminal(equipmentDO, loginUserDTO, companyDO);
            equipmentDao.insert(equipmentDO);
            BeanUtils.copyProperties(equipmentDO, loginUserDTO);
            loginUserDTO.setId(equipmentDO.getId());
            // 发送用户激活事件
            pushUserLoginEvent(equipmentDO, loginUserDTO);
        }

        UsernameAuthenticationToken authenticationResult = new UsernameAuthenticationToken(userDetails.getAuthorities(), userDetails, imei, oaid, uuid, companyId);
        authenticationResult.setDetails(authentication.getDetails());
        return authenticationResult;
    }

    private void pushUserLoginEvent(DeviceEquipmentDO equipmentDO, LoginUserDTO loginUserDTO) {
        UserLoginCallbackDTO userLoginCallbackDTO = UserLoginCallbackDTO.builder()
                .deviceIdList(ListUtil.toList(equipmentDO.getId()))
                .companyId(loginUserDTO.getCompanyId())
                .departmentId(equipmentDO.getDepartmentId())
                .build();
        rabbitMQProvider.publishRegisterEventWithDelayed(userLoginCallbackDTO);
    }

    private void checkLicenseLimit(LoginUserDTO loginUserDTO, AuCompanyDO companyDO) {
        String allowUseNumber = companyDO.getAllowUseNumber();
        SM2 sm2 = SmUtils.sm2();
        String authorizationNumber = StrUtil.utf8Str(sm2.decryptFromBcd(allowUseNumber, KeyType.PrivateKey));

        Long equipmentCount = new LambdaQueryChainWrapper<>(equipmentDao)
                .eq(DeviceEquipmentDO::getCompanyId, loginUserDTO.getCompanyId())
                .eq(DeviceEquipmentDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState())
                .count();
        if (equipmentCount >= Integer.parseInt(authorizationNumber)) {
            throw new NumberLimitException("设备数量超出限制，无法注册，请联系管理员!");
        }
    }

    private String getEquipmentName(Integer userId, String userName, Integer companyId) {
        List<String> equipmentNameList = new LambdaQueryChainWrapper<>(equipmentDao)
                .likeRight(DeviceEquipmentDO::getEquipmentName, userName)
                .eq(DeviceEquipmentDO::getUserId, userId)
                .eq(DeviceEquipmentDO::getCompanyId, companyId)
                .eq(DeviceEquipmentDO::getIsDelete, DeleteStateEnum.NON_DELETE.getState())
                .list()
                .stream().map(DeviceEquipmentDO::getEquipmentName)
                .collect(Collectors.toList());
        return RegisterEquipmentUtil.createEquipmentName(equipmentNameList, userName);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return (UsernameAuthenticationToken.class.isAssignableFrom(authentication));
    }

    public void setUserDetailsService(UsernameDetailsServiceImpl userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    public void setEquipmentDao(DeviceEquipmentDao equipmentDao) {
        this.equipmentDao = equipmentDao;
    }

    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    public void setCompanyDao(AuCompanyDao companyDao) {
        this.companyDao = companyDao;
    }

    public void setRabbitMQProvider(RabbitMQProvider rabbitMQProvider) {
        this.rabbitMQProvider = rabbitMQProvider;
    }

}