package org.dromara.web.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.hotwj.platform.driverManagement.driver.domain.HotDriver;
import com.hotwj.platform.driverManagement.driver.mapper.HotDriverMapper;
import com.hotwj.platform.resourceManagement.company.domain.SysUserLoginPort;
import com.hotwj.platform.resourceManagement.company.domain.vo.SysUserLoginPortVo;
import com.hotwj.platform.resourceManagement.company.service.ISysUserLoginPortService;
import com.hotwj.platform.resourceManagement.companySafetyManager.domain.HotCompanySafetyManager;
import com.hotwj.platform.resourceManagement.companySafetyManager.mapper.HotCompanySafetyManagerMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.domain.model.PasswordLoginBody;
import org.dromara.common.core.enums.LoginType;
import org.dromara.common.core.exception.user.CaptchaException;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.core.utils.MessageUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.tenant.helper.TenantHelper;
import org.dromara.common.web.config.properties.CaptchaProperties;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.service.ISysUserService;
import org.dromara.web.domain.vo.LoginVo;
import org.dromara.web.service.IAuthStrategy;
import org.dromara.web.service.SysLoginService;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 密码认证策略
 *
 * @author shihongwei
 */
@Slf4j
@Service("password" + IAuthStrategy.BASE_NAME)
@RequiredArgsConstructor
public class PasswordAuthStrategy implements IAuthStrategy {

    private final CaptchaProperties captchaProperties;
    private final SysLoginService loginService;
    private final ISysUserService userService;
    private final ISysUserLoginPortService sysUserLoginPortService;
    private final HotDriverMapper hotDriverMapper;
    private final HotCompanySafetyManagerMapper hotCompanySafetyManagerMapper;

    /**
     * 登录逻辑
     */
    @Override
    public LoginVo login(String body, SysClientVo client) {
        PasswordLoginBody loginBody = JsonUtils.parseObject(body, PasswordLoginBody.class);
        ValidatorUtils.validate(loginBody);

        // 1. 验证码校验
        checkCaptcha(loginBody);

        // 2. 核心认证 (校验用户与密码)
        LoginUser loginUser = authenticateUser(loginBody);

        // 3. 执行登录
        if (LoginHelper.isSuperAdmin(loginUser.getUserId())) {
            // 超级管理员直接登录，跳过端口逻辑
            return loginAsSuperAdmin(client, loginUser);
        }

        return loginAsNormalUser(client, loginUser, loginBody);
    }

    /**
     * 校验验证码
     */
    private void checkCaptcha(PasswordLoginBody loginBody) {
        if (captchaProperties.getEnable()) {
            validateCaptcha(loginBody.getTenantId(), loginBody.getAccount(), loginBody.getCode(), loginBody.getUuid());
        }
    }

    /**
     * 核心认证逻辑
     */
    private LoginUser authenticateUser(PasswordLoginBody loginBody) {
        String tenantId = loginBody.getTenantId();
        String account = loginBody.getAccount();
        String password = loginBody.getPassword();

        // 存储密码验证通过的端口ID
        Set<Long> matchingPortIds = new HashSet<>();

        return TenantHelper.dynamic(tenantId, () -> {
            SysUserVo user = userService.selectUserByPhonenumber(account);
            if (ObjectUtil.isNull(user)) {
                log.info("登录用户：{} 不存在.", account);
                throw new UserException("user.not.exists", account);
            } else if (SystemConstants.DISABLE.equals(user.getStatus())) {
                log.info("登录用户：{} 已被停用.", account);
                throw new UserException("user.blocked", account);
            }

            loginService.checkLogin(LoginType.PASSWORD, tenantId, account, () -> {
                // 如果是超级管理员，走原来的密码验证
                if (LoginHelper.isSuperAdmin(user.getUserId())) {
                    return !BCrypt.checkpw(password, user.getPassword());
                }

                // 普通用户，校验子密码
                List<SysUserLoginPort> ports = sysUserLoginPortService.list(new LambdaQueryWrapper<SysUserLoginPort>()
                        .eq(SysUserLoginPort::getUserId, user.getUserId())
                        .eq(SysUserLoginPort::getStatus, 1));

                boolean matchFound = false;
                for (SysUserLoginPort port : ports) {
                    String subPwd = port.getSubPassword();
                    // 仅校验子密码，如果不匹配则跳过
                    if (StringUtils.isNotBlank(subPwd) && BCrypt.checkpw(password, subPwd)) {
                        matchingPortIds.add(port.getId());
                        matchFound = true;
                    }
                }

                if (!matchFound) {
                    assertNotResignedUser(user.getUserId());
                }

                return !matchFound;
            });
            // 此处可根据登录用户的数据不同 自行创建 loginUser
            LoginUser loginUser = loginService.buildLoginUser(user);
            if (!matchingPortIds.isEmpty()) {
                loginUser.setAllowedPortIds(matchingPortIds);
            }
            return loginUser;
        });
    }

    /**
     * 当启用端口都未匹配到密码时，补充识别“离职人员使用历史子密码登录”的场景，
     * 避免前端误提示为密码错误。
     */
    private void assertNotResignedUser(Long userId) {
        Long enabledEnterprisePortCount = sysUserLoginPortService.count(new LambdaQueryWrapper<SysUserLoginPort>()
                .eq(SysUserLoginPort::getUserId, userId)
                .eq(SysUserLoginPort::getStatus, 1)
                .eq(SysUserLoginPort::getLoginPort, ISysUserLoginPortService.ENTERPRISE_PORT));
        if (enabledEnterprisePortCount != null && enabledEnterprisePortCount > 0) {
            return;
        }

        List<SysUserLoginPort> disabledPorts = sysUserLoginPortService.list(new LambdaQueryWrapper<SysUserLoginPort>()
                .eq(SysUserLoginPort::getUserId, userId)
                .eq(SysUserLoginPort::getStatus, 0)
                .eq(SysUserLoginPort::getLoginPort, ISysUserLoginPortService.ENTERPRISE_PORT));

        if (CollUtil.isEmpty(disabledPorts)) {
            return;
        }

        for (SysUserLoginPort port : disabledPorts) {
            Long resignedCount = hotCompanySafetyManagerMapper.selectCount(new LambdaQueryWrapper<HotCompanySafetyManager>()
                    .eq(HotCompanySafetyManager::getCompanyId, port.getCompanyId())
                    .eq(HotCompanySafetyManager::getUserId, userId)
                    .eq(HotCompanySafetyManager::getStatus, 1L)
                    .eq(HotCompanySafetyManager::getIsResigned, 1L));
            if (resignedCount != null && resignedCount > 0) {
                throw new UserException("该人员已离职，无法登录");
            }
        }
    }

    /**
     * 超级管理员登录
     */
    private LoginVo loginAsSuperAdmin(SysClientVo client, LoginUser loginUser) {
        // 管理员直接登录，不进行多租户判断
        loginUser = loginService.buildLoginUserPerm(loginUser, null, true, null);
        return doLogin(client, loginUser, true, null);
    }

    @NotNull
    private static LoginVo doLogin(SysClientVo client, LoginUser loginUser, boolean isAdmin, SysUserLoginPortVo companyRole) {
        // 设置是否为管理员
        loginUser.setIsAdmin(isAdmin);
        if (companyRole != null) {
            loginUser.setCompanyRoleId(companyRole.getId());
            loginUser.setCompanyId(companyRole.getCompanyId());
            loginUser.setCompanyName(companyRole.getCompanyName());
            loginUser.setUsername(companyRole.getUsername());
        } else if (isAdmin) {
            loginUser.setCompanyId(1L);
            loginUser.setLoginPort(ISysUserLoginPortService.HEADQUARTERS_PORT);
        }


        loginUser.setClientKey(client.getClientKey());
        loginUser.setDeviceType(client.getDeviceType());
        SaLoginParameter model = new SaLoginParameter();
        model.setDeviceType(client.getDeviceType());
        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
        // 例如: 后台用户30分钟过期 app用户1天过期
        model.setTimeout(client.getTimeout());
        model.setActiveTimeout(client.getActiveTimeout());
        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
//        model.setMaxLoginCount(-1);

        // 生成token
        LoginHelper.login(loginUser, model);

        LoginVo loginVo = new LoginVo();
        loginVo.setAccessToken(StpUtil.getTokenValue());
        loginVo.setExpireIn(StpUtil.getTokenTimeout());
        loginVo.setClientId(client.getClientId());
        if (companyRole != null) {
            loginVo.setCompanyId(companyRole.getCompanyId());
        } else if (isAdmin) {
            loginVo.setCompanyId(1L);
        }
        return loginVo;
    }

    /**
     * 普通用户登录 (包含多端口/多角色处理)
     */
    private LoginVo loginAsNormalUser(SysClientVo client, LoginUser loginUser, PasswordLoginBody loginBody) {
        Long userId = loginUser.getUserId();
        List<SysUserLoginPortVo> allRoles = sysUserLoginPortService.selectLoginPortListByPort(userId, null);

        if (CollUtil.isNotEmpty(loginUser.getAllowedPortIds())) {
            allRoles = allRoles.stream()
                    .filter(r -> loginUser.getAllowedPortIds().contains(r.getId()))
                    .collect(Collectors.toList());
        }

        List<SysUserLoginPortVo> currentPortRoles;
        if (!"app".equals(client.getClientKey())) {
            if (StringUtils.isBlank(loginBody.getLoginPort())) {
                List<String> availablePorts = allRoles.stream()
                        .map(SysUserLoginPortVo::getLoginPort)
                        .filter(StringUtils::isNotBlank)
                        .distinct()
                        .collect(Collectors.toList());
                if (CollUtil.isEmpty(availablePorts)) {
                    log.info("登录用户：{} 不存在可登录端口或密码错误.", loginBody.getAccount());
                    throw new UserException("当前账号无可登录端口或密码错误", loginBody.getAccount());
                }
                if (availablePorts.size() > 1) {
                    return buildPortSelectionResponse(availablePorts, allRoles);
                }
                currentPortRoles = filterRolesByPort(allRoles, availablePorts.get(0));
            } else {
                currentPortRoles = filterRolesByPort(allRoles, loginBody.getLoginPort());
            }
            if (CollUtil.isEmpty(currentPortRoles)) {
                log.info("登录用户：{} 不存在当前端口角色或密码错误.", loginBody.getAccount());
                throw new UserException("当前端口不存在该用户或密码错误", loginBody.getAccount());
            }
        } else {
            currentPortRoles = allRoles.stream()
                .filter(role -> ISysUserLoginPortService.ENTERPRISE_PORT.equals(role.getLoginPort())
                    || ISysUserLoginPortService.DRIVER_PORT.equals(role.getLoginPort()))
                    .collect(Collectors.toList());

            if (CollUtil.isEmpty(currentPortRoles)) {
                log.info("移动端登录用户：{} 不存在企业或司机角色或密码错误.", loginBody.getAccount());
                throw new UserException("当前账号无移动端登录权限或密码错误", loginBody.getAccount());
            }
        }

        // 场景1: 指定了具体角色ID (切换身份或明确登录)
        if (loginBody.getCompanyRoleId() != null) {
            return loginWithSpecificRole(client, loginUser, allRoles, loginBody.getCompanyRoleId(), currentPortRoles);
        }

        // 场景2: 该端口下只有一个角色 (自动登录)
        if (currentPortRoles.size() == 1) {
            return loginWithSpecificRole(client, loginUser, allRoles, currentPortRoles.get(0).getId(), currentPortRoles);
        }

        // 场景3: 多角色且未指定 (返回列表供选择)
        return buildMultiRoleResponse(currentPortRoles, allRoles);
    }

    /**
     * 过滤端口角色
     */
    private List<SysUserLoginPortVo> filterRolesByPort(List<SysUserLoginPortVo> allRoles, String loginPort) {
        return allRoles.stream()
            .filter(role -> StringUtils.isBlank(loginPort) || role.getLoginPort().equals(loginPort))
            .toList();
    }

    /**
     * 指定角色登录
     */
    private LoginVo loginWithSpecificRole(SysClientVo client, LoginUser loginUser, List<SysUserLoginPortVo> allRoles, Long roleId, List<SysUserLoginPortVo> currentPortRoles) {
        SysUserLoginPortVo selectedRole = currentPortRoles.stream()
            .filter(item -> roleId.equals(item.getId()))
            .findFirst()
            .orElseThrow(() -> new UserException("当前端口不存在该用户"));

        // 在此处设置权限
        loginUser = loginService.buildLoginUserPerm(loginUser, selectedRole.getCompanyId(), false, selectedRole.getLoginPort());

        // 设置业务ID和用户类型
        if (ISysUserLoginPortService.ENTERPRISE_PORT.equals(selectedRole.getLoginPort())) {
            // 企业端：通过 userId + companyId 唯一确定人员信息
            // 考虑到可能存在多条记录（如离职重聘），取最新的一条
            LambdaQueryWrapper<HotCompanySafetyManager> lqw = new LambdaQueryWrapper<>();
            lqw.eq(HotCompanySafetyManager::getCompanyId, selectedRole.getCompanyId());
            lqw.eq(HotCompanySafetyManager::getUserId, loginUser.getUserId());
            lqw.eq(HotCompanySafetyManager::getStatus, 1L);
            lqw.eq(HotCompanySafetyManager::getIsResigned, 0L);
            lqw.orderByDesc(HotCompanySafetyManager::getCreateTime);
            lqw.last("limit 1");

            HotCompanySafetyManager manager = hotCompanySafetyManagerMapper.selectOne(lqw);
            if (manager != null) {
                loginUser.setBusinessUserId(String.valueOf(manager.getId()));
            }
        } else if (ISysUserLoginPortService.DRIVER_PORT.equals(selectedRole.getLoginPort())) {
            SysUserVo sysUser = userService.selectUserById(Long.valueOf(loginUser.getUserId()));
            if (sysUser != null) {
                // 驾驶员端：通过 phone + companyId 唯一确定驾驶员信息
                // 考虑到可能存在多条记录（如历史数据），取最新的一条
                LambdaQueryWrapper<HotDriver> lqw = new LambdaQueryWrapper<>();
                lqw.eq(HotDriver::getPhone, sysUser.getPhonenumber());
                lqw.eq(HotDriver::getCompanyId, selectedRole.getCompanyId());
                lqw.eq(HotDriver::getStatus, 1L);
                lqw.orderByDesc(HotDriver::getCreateTime);
                lqw.last("limit 1");

                HotDriver driver = hotDriverMapper.selectOne(lqw);
                if (driver != null) {
                    loginUser.setBusinessUserId(driver.getId());
                } else {
                    throw new UserException("该驾驶员已离职或档案不存在，无法登录");
                }
            }
        }

        LoginVo loginVo = doLogin(client, loginUser, false, selectedRole);
        // 返回所有端口信息
        loginVo.setRoles(currentPortRoles);
        loginVo.setAllRoles(allRoles);
        return loginVo;
    }

    /**
     * 构建多角色选择响应
     */
    private LoginVo buildMultiRoleResponse(List<SysUserLoginPortVo> currentPortRoles, List<SysUserLoginPortVo> allRoles) {
        LoginVo loginVo = new LoginVo();
        loginVo.setRoles(currentPortRoles);
        loginVo.setAllRoles(allRoles);
        return loginVo;
    }

    private LoginVo buildPortSelectionResponse(List<String> loginPorts, List<SysUserLoginPortVo> allRoles) {
        LoginVo loginVo = new LoginVo();
        loginVo.setLoginPorts(loginPorts);
        loginVo.setAllRoles(allRoles);
        return loginVo;
    }

    /**
     * 校验验证码
     *
     * @param username 用户名
     * @param code     验证码
     * @param uuid     唯一标识
     */
    private void validateCaptcha(String tenantId, String username, String code, String uuid) {
        String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + StringUtils.blankToDefault(uuid, "");
        String captcha = RedisUtils.getCacheObject(verifyKey);
        RedisUtils.deleteObject(verifyKey);
        if (captcha == null) {
            loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
            throw new CaptchaExpireException();
        }
        if (!StringUtils.equalsIgnoreCase(code, captcha)) {
            loginService.recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
            throw new CaptchaException();
        }
    }


}
