package com.jwipc.sys.logic.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgentUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.net.HttpHeaders;
import com.jwipc.constant.DelFlag;
import com.jwipc.exception.DaoException;
import com.jwipc.security.LoginUtil;
import com.jwipc.security.dto.CurrentUserDTO;
import com.jwipc.security.prop.SecurityProp;
import com.jwipc.supplier.clent.dto.SupplierDTO;
import com.jwipc.supplier.clent.dto.SupplierTemplateDTO;
import com.jwipc.supplier.client.SupplierClient;
import com.jwipc.sys.client.DictClient;
import com.jwipc.sys.client.dto.DictDTO;
import com.jwipc.sys.constant.SysConfigStatus;
import com.jwipc.sys.constant.UserStatus;
import com.jwipc.sys.constant.UserType;
import com.jwipc.sys.dto.LoginPageAttrDTO;
import com.jwipc.sys.dto.LoginParamDTO;
import com.jwipc.sys.dto.ResBtnDTO;
import com.jwipc.sys.entity.*;
import com.jwipc.sys.logic.LoginLogic;
import com.jwipc.sys.service.*;
import com.jwipc.util.PasswordUtilSHA256;
import com.jwipc.util.RequestUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Sets;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.MessageSource;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


/**
 * <p> </p>
 *
 * @author BOGON
 * @version 1.0
 * @since JDK 1.8
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class LoginLogicImpl implements LoginLogic {
    private final ISysConfigService sysConfigService;
    private final IUserService spUserService;
    private final StringRedisTemplate redisTemplate;
    private final MessageSource messageSource;
    private final IUserRoleService userRoleService;
    private final IResourcesService resourcesService;
    private final IRoleResService roleResService;
    private final IButtonService buttonService;
    private final IRoleResButtonService roleResButtonService;
    private final IUserLoginService userLoginService;
    private final IOrgService orgService;
    public static String LOCK_REDIS_PREFIX = "LOGIN_LOCK_";
    private final SecurityProp securityProp;
    private final SupplierClient supplierClient;
    private final IRoleService roleService;
    private final DictClient dictClient;

    @Override
    public LoginPageAttrDTO renderHanlder(Set<String> sysConfigKeys) {

        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        final SupplierTemplateDTO one = supplierClient.getSupplierDefaultTemplate();
        final LoginPageAttrDTO.LoginPageAttrDTOBuilder builder = LoginPageAttrDTO.builder();
        if (Objects.nonNull(one)) {
            builder.supplierDefaultTemplateId(String.valueOf(one.getId()));
        }


        Map<String, String> map = Maps.newHashMap();
        final LambdaQueryWrapper<SysConfig> sysConfigLambdaQueryWrapper = new LambdaQueryWrapper<SysConfig>().eq(
                SysConfig::getStatus, SysConfigStatus.ENABLE.getStatus()).in(SysConfig::getConfigKey, sysConfigKeys);
        final List<SysConfig> list = sysConfigService.list(sysConfigLambdaQueryWrapper);

        for (SysConfig sc : list) {
            map.put(sc.getConfigKey(), sc.getConfigValue());
        }

        for (String key : sysConfigKeys) {
            if (StringUtils.isEmpty(map.get(key))) {
                map.put(key, "");
            }
        }
        builder.configs(map);
        return builder.build();
    }

    @Override
    public List<ResBtnDTO> login(HttpServletRequest request, LoginParamDTO loginParamDTO) {
        final LambdaQueryWrapper<User> sserLambdaQueryWrapper = new LambdaQueryWrapper<User>().eq(
                        User::getAccountName, loginParamDTO.getUsername())
                .eq(User::getStatus, UserStatus.ENABLE.getStatus()).eq(User::getDeleteStatus,
                        DelFlag.NORMAL.getStatus());
        final User one = spUserService.getOne(sserLambdaQueryWrapper);
        if (Objects.isNull(one)) {
            throw new DaoException("用户名或密码错误");
        }
        // 判断访问用户的网络环境，限制内部用户只能在内网访问
        checkInnerUserNetwork(request, one);
        Locale locale = LocaleContextHolder.getLocale();
        String times = redisTemplate.opsForValue().get(LOCK_REDIS_PREFIX + loginParamDTO.getUsername());
        if (!Objects.isNull(times) && Integer.valueOf(times).compareTo(securityProp.getLockTimes()) >= 0) {
            String[] params = {times, String.valueOf(securityProp.getLockMinute())};
            throw new DaoException(messageSource.getMessage("PasswordErrorTimesMoreThan", params, locale));
        }
        if (Objects.isNull(times)) {
            redisTemplate.opsForValue().set(LOCK_REDIS_PREFIX + loginParamDTO.getUsername(), String.valueOf(1),
                    securityProp.getLockTimes(), TimeUnit.MINUTES);
        } else {
            redisTemplate.opsForValue()
                    .set(LOCK_REDIS_PREFIX + loginParamDTO.getUsername(), String.valueOf(Integer.valueOf(times) + 1),
                            securityProp.getLockTimes(), TimeUnit.MINUTES);
        }
        final byte[] bytes = Objects.isNull(one.getCredentialsSalt()) ? null : one.getCredentialsSalt().getBytes();
        String userPass = PasswordUtilSHA256.password(bytes, loginParamDTO.getPassword());
        if (!StrUtil.equals(userPass, one.getPassword())) {
            throw new DaoException("用户名或密码错误");
        }
        final CurrentUserDTO currentUserDTO = buildLoginUserDTO(one);
        StpUtil.login(one.getId());
        StpUtil.getSession().set(securityProp.getUserKey(), currentUserDTO);
        recordLoginLogs(currentUserDTO);
        log.info("{}", LoginUtil.getCurrentUser());
        //把当前用的角色按钮组装
        unLock(one.getAccountName());
        return getRoleResBtn(currentUserDTO.getRoleIds());
    }

    private CurrentUserDTO buildLoginUserDTO(User one) {
        final CurrentUserDTO.CurrentUserDTOBuilder builder = CurrentUserDTO.builder();
        final Pair<List<Long>, List<String>> roleIdsAndCodes = getRoleIdsAndCodes(one.getId());
        builder.roleIds(roleIdsAndCodes.getKey());
        builder.roles(roleIdsAndCodes.getValue());
        builder.loginName(one.getAccountName());
        builder.name(one.getUsername());
        builder.phone(one.getPhone());
        builder.userId(one.getId());
        builder.email(one.getEmail());
        builder.supplierId(one.getSupplierId());
        builder.isFirstLogin(one.getIsFirstLogin());
        if (one.getUserType().equals(UserType.SUPPLIER.getType())) {
            final SupplierDTO supplierDTO = supplierClient.getSupplierById((one.getSupplierId()));
            builder.supplierCode(supplierDTO.getSupplierCode());
            builder.supplierName(supplierDTO.getSupplierName());
        }
        //需要获取部门名称
        builder.deptCode(one.getDeptCode());
        if (StrUtil.isNotEmpty(one.getDeptCode())) {
            final Org org = orgService.getOne(new LambdaQueryWrapper<Org>().eq(Org::getCode, one.getDeptCode()),
                    false);
            if (Objects.nonNull(org)) {
                builder.deptName(org.getName());
            }
        }
        return builder.build();
    }

    private void recordLoginLogs(CurrentUserDTO userDTO) {
        final HttpServletRequest request =
                ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        final String userAgentReq = request.getHeader(HttpHeaders.USER_AGENT);
        UserLogin userLogin = new UserLogin();
        userLogin.setLoginIp(RequestUtil.getIpAddress(request));
        userLogin.setLoginTime(LocalDateTime.now());
        userLogin.setUserId(userDTO.getUserId());
        userLogin.setUserName(userDTO.getName());
        userLogin.setUserAgent(userAgentReq);
        userLogin.setDeviceName(UserAgentUtil.parse(userAgentReq).getPlatform().getName());
        userLogin.setSupplierId(userDTO.getSupplierId());
        userLogin.setSupplierName(userDTO.getSupplierName());
        userLogin.setAccountName(userDTO.getLoginName());
        userLoginService.save(userLogin);
    }

    @Override
    public Pair<List<Long>, List<String>> getRoleIdsAndCodes(Long userId) {
        //需要查询当前用的所有角色信息
        final LambdaQueryWrapper<UserRole> userRoleLambdaQueryWrapper = new LambdaQueryWrapper<UserRole>().eq(
                UserRole::getUserId, userId);
        final List<UserRole> list = userRoleService.list(userRoleLambdaQueryWrapper);
        List<Long> roleIds = Lists.newArrayList();
        List<String> roleCodes = Lists.newArrayList();
        if (CollUtil.isNotEmpty(list)) {
            roleIds = list.stream().map(UserRole::getRoleId).collect(Collectors.toList());
            final List<Role> resources = roleService.listByIds(roleIds);
            roleCodes = resources.stream().map(Role::getRolekey).collect(Collectors.toList());
            return Pair.of(roleIds, roleCodes);
        }
        return Pair.of(roleIds, roleCodes);
    }

    private List<ResBtnDTO> getRoleResBtn(List<Long> role) {
        if (Objects.isNull(role)) {
            return Collections.emptyList();
        }
        final List<RoleRes> list = roleResService.list(new LambdaQueryWrapper<RoleRes>().in(RoleRes::getRoleId, role));
        final List<RoleResButton> roleResButtons = roleResButtonService.list(
                new LambdaQueryWrapper<RoleResButton>().in(RoleResButton::getRoleId, role));
        final List<Long> resIds = list.stream().map(RoleRes::getResId).collect(Collectors.toList());
        final List<Resources> resources = resourcesService.listByIds(resIds);
        final Map<Long, String> resIdKeyMap = resources.stream()
                .collect(Collectors.toMap(Resources::getId, Resources::getResKey));
        final List<Button> allBtns = buttonService.list();
        final Map<Long, String> btnIdKeyMap = allBtns.stream()
                .collect(Collectors.toMap(Button::getId, Button::getButton));
        Map<String, ResBtnDTO> map = Maps.newHashMap();
        for (RoleResButton roleRes : roleResButtons) {
            final String resKey = resIdKeyMap.get(roleRes.getResId());
            ResBtnDTO resBtnDTO = map.get(resKey);
            if (Objects.nonNull(resBtnDTO)) {
                resBtnDTO.getBtns().add(btnIdKeyMap.get(roleRes.getButtonId()));
            } else {
                resBtnDTO = new ResBtnDTO();
                resBtnDTO.setResKey(resKey);
                resBtnDTO.setBtns(Sets.newHashSet(btnIdKeyMap.get(roleRes.getButtonId())));
                map.put(resKey, resBtnDTO);
            }
        }
        return map.values().stream().toList();
    }

    @Override
    public void unLock(String accountName) {
        redisTemplate.delete(LOCK_REDIS_PREFIX + accountName);
    }

    @Override
    public List<ResBtnDTO> noPasswordLogin(Long userId) {
        final User one = spUserService.getById(userId);
        if (one.getDeleteStatus() != DelFlag.NORMAL.getStatus()) {
            throw new DaoException("账号已禁用!");
        }
        StpUtil.login(one.getId());
        final CurrentUserDTO currentUserDTO = buildLoginUserDTO(one);
        StpUtil.getSession().set(securityProp.getUserKey(), currentUserDTO);
        recordLoginLogs(currentUserDTO);
        return getRoleResBtn(currentUserDTO.getRoleIds());
    }

    /**
     * 判断访问用户的网络环境，限制内部用户只能在内网访问
     * @param request 请求头
     * @param user  用户信息
     */
    private void checkInnerUserNetwork(HttpServletRequest request, User user) {
        String ip = request.getRemoteAddr();

        List<DictDTO> systemAllowedIPDictList = dictClient.getDicByType("system_allowed_ip");
        List<String> systemAllowedIPList = systemAllowedIPDictList.stream()
                .map(DictDTO::getRemark)
                .filter(StringUtils::isNotEmpty)
                .toList();
        if (CollUtil.isEmpty(systemAllowedIPList)) {
            // 没有配置系统允许的IP，则全部允许
            return;
        }
        boolean isInternalIP = systemAllowedIPList.stream().anyMatch(ip::startsWith);
        // user_type：0=内部用户, 1=供应商
        Integer userType = user.getUserType();

        // ---------------------
        //   逻辑判断部分
        // ---------------------
        if (isInternalIP) {
            // 内网全部放行
            return;
        }

        // 外网访问
        if (userType == 1) {
            // 供应商用户 → 放行
            return;
        }

        if (userType == 0) {
            recordLoginLogs(buildLoginUserDTO(user));
            // 内部用户 → 禁止不使用零信任
            throw new DaoException("未登入零信任，请先登录零信任！");
        }
    }
}