 package org.sky.auth.handler;

 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.sky.auth.constant.KdAuthConstant;
 import org.sky.common.cache.CacheNames;
 import org.sky.common.constant.TenantConstant;
 import org.sky.core.launch.props.KdProperties;
 import org.sky.core.oauth2.exception.ExceptionCode;
 import org.sky.core.oauth2.handler.AbstractAuthorizationHandler;
 import org.sky.core.oauth2.props.OAuth2Properties;
 import org.sky.core.oauth2.provider.OAuth2Request;
 import org.sky.core.oauth2.provider.OAuth2Validation;
 import org.sky.core.oauth2.service.OAuth2User;
 import org.sky.core.redis.cache.KdRedis;
 import org.sky.core.tenant.KdTenantProperties;
 import org.sky.core.tool.jackson.JsonUtil;
 import org.sky.core.tool.utils.*;
 import org.sky.system.cache.ParamCache;
 import org.sky.system.cache.SysCache;
 import org.sky.system.pojo.entity.Tenant;

 import java.time.Duration;
 import java.util.Date;
 import java.util.List;

/**
 * KdAuthorizationHandler
 *
 */
@Slf4j
@RequiredArgsConstructor
public class KdAuthorizationHandler extends AbstractAuthorizationHandler {

	private final KdRedis kdRedis;
	private final KdProperties kdProperties;
	private final KdTenantProperties tenantProperties;
	private final OAuth2Properties oAuth2Properties;

	/**
	 * 自定义弱密码列表
	 */
	private static final List<String> WEAK_PASSWORDS = List.of("admin", "hr", "manager", "boss");

	/**
	 * 认证前校验
	 *
	 * @param request 请求信息
	 * @return boolean
	 */
	@Override
	public OAuth2Validation preValidation(OAuth2Request request) {
		if (request.isPassword() || request.isCaptchaCode()) {
			// 生产环境弱密码校验
			if (kdProperties.isProd() && isWeakPassword(request.getPassword())) {
				return buildValidationFailure(ExceptionCode.INVALID_USER_PASSWORD);
			}
			// 判断登录是否锁定
			OAuth2Validation failCountValidation = validateFailCount(request.getTenantId(), request.getUsername());
			if (!failCountValidation.isSuccess()) {
				return failCountValidation;
			}
		}
		return super.preValidation(request);
	}

	/**
	 * 认证前失败回调
	 *
	 * @param validation 失败信息
	 */
	@Override
	public void preFailure(OAuth2Request request, OAuth2Validation validation) {
		// 增加错误锁定次数
		addFailCount(request.getTenantId(), request.getUsername());

		log.error("用户：{}，认证失败，失败原因：{}", request.getUsername(), validation.getMessage());
	}

	/**
	 * 认证校验
	 *
	 * @param user    用户信息
	 * @param request 请求信息
	 * @return boolean
	 */
	@Override
	public OAuth2Validation authValidation(OAuth2User user, OAuth2Request request) {
		// 密码模式、刷新token模式、验证码模式需要校验租户状态
		if (request.isPassword() || request.isRefreshToken() || request.isCaptchaCode()) {
			// 租户校验
			OAuth2Validation tenantValidation = validateTenant(user.getTenantId());
			if (!tenantValidation.isSuccess()) {
				return tenantValidation;
			}
			// 判断登录是否锁定
			OAuth2Validation failCountValidation = validateFailCount(user.getTenantId(), user.getAccount());
			if (!failCountValidation.isSuccess()) {
				return failCountValidation;
			}
		}
		return super.authValidation(user, request);
	}

	/**
	 * 认证成功回调
	 *
	 * @param user 用户信息
	 */
	@Override
	public void authSuccessful(OAuth2User user, OAuth2Request request) {
		// 清空错误锁定次数
		delFailCount(user.getTenantId(), user.getAccount());

		log.info("用户：{}，认证成功", user.getAccount());
	}

	/**
	 * 认证失败回调
	 *
	 * @param user       用户信息
	 * @param validation 失败信息
	 */
	@Override
	public void authFailure(OAuth2User user, OAuth2Request request, OAuth2Validation validation) {
		// 自定义认证失败回调
	}

	/**
	 * 判断是否为弱密码
	 *
	 * @param rawPassword 加密密码
	 * @return boolean
	 */
	private boolean isWeakPassword(String rawPassword) {
		// 获取公钥
		String publicKey = oAuth2Properties.getPublicKey();
		// 获取私钥
		String privateKey = oAuth2Properties.getPrivateKey();
		// 解密密码
		String decryptPassword = SM2Util.decrypt(rawPassword, publicKey, privateKey);
		return WEAK_PASSWORDS.stream()
			.anyMatch(weakPass -> weakPass.equalsIgnoreCase(decryptPassword));
	}

	/**
	 * 租户授权校验
	 *
	 * @param tenantId 租户id
	 * @return OAuth2Validation
	 */
	private OAuth2Validation validateTenant(String tenantId) {
		// 租户校验
		Tenant tenant = SysCache.getTenant(tenantId);
		if (tenant == null) {
			return buildValidationFailure(ExceptionCode.USER_TENANT_NOT_FOUND);
		}
		// 租户授权时间校验
		Date expireTime = tenant.getExpireTime();
		if (tenantProperties.getLicense()) {
			String licenseKey = tenant.getLicenseKey();
			String decrypt = DesUtil.decryptFormHex(licenseKey, TenantConstant.DES_KEY);
			Tenant license = JsonUtil.parse(decrypt, Tenant.class);
			if (license == null || !license.getId().equals(tenant.getId())) {
				return buildValidationFailure(ExceptionCode.UNAUTHORIZED_USER_TENANT);
			}
			expireTime = license.getExpireTime();
		}
		if (expireTime != null && expireTime.before(DateUtil.now())) {
			return buildValidationFailure(ExceptionCode.UNAUTHORIZED_USER_TENANT);
		}
		return new OAuth2Validation();
	}

	/**
	 * 判断登录是否锁定
	 *
	 * @param tenantId 租户id
	 * @param account  账号
	 * @return OAuth2Validation
	 */
	private OAuth2Validation validateFailCount(String tenantId, String account) {
		int cnt = getFailCount(tenantId, account);
		int failCount = Func.toInt(ParamCache.getValue(KdAuthConstant.FAIL_COUNT_VALUE), KdAuthConstant.FAIL_COUNT);
		if (cnt >= failCount) {
			log.error("用户：{}，已锁定，请求ip：{}", account, WebUtil.getIP());
			return buildValidationFailure(ExceptionCode.USER_TOO_MANY_FAILS);
		}
		return new OAuth2Validation();
	}

	/**
	 * 获取账号错误次数
	 *
	 * @param tenantId 租户id
	 * @param username 账号
	 * @return int
	 */
	private int getFailCount(String tenantId, String username) {
		if (Func.hasEmpty(tenantId, username)) {
			return 0;
		}
		return Func.toInt(kdRedis.get(CacheNames.tenantKey(tenantId, CacheNames.USER_FAIL_KEY, username)), 0);
	}

	/**
	 * 设置账号错误次数
	 *
	 * @param tenantId 租户id
	 * @param username 账号
	 */
	private void addFailCount(String tenantId, String username) {
		if (Func.hasEmpty(tenantId, username)) {
			return;
		}
		int count = getFailCount(tenantId, username);
		kdRedis.setEx(CacheNames.tenantKey(tenantId, CacheNames.USER_FAIL_KEY, username), count + 1, Duration.ofMinutes(30));
	}

	/**
	 * 设置账号错误次数
	 *
	 * @param tenantId 租户id
	 * @param username 账号
	 * @param count    次数
	 */
	private void setFailCount(String tenantId, String username, int count) {
		kdRedis.setEx(CacheNames.tenantKey(tenantId, CacheNames.USER_FAIL_KEY, username), count + 1, Duration.ofMinutes(30));
	}

	/**
	 * 清空账号错误次数
	 *
	 * @param tenantId 租户id
	 * @param username 账号
	 */
	private void delFailCount(String tenantId, String username) {
		kdRedis.del(CacheNames.tenantKey(tenantId, CacheNames.USER_FAIL_KEY, username));
	}
}
