package com.ichangzuo.module.auth.service.impl;

import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.captcha.generator.RandomGenerator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.jwt.JWTPayload;
import cn.hutool.jwt.JWTUtil;
import com.ichangzuo.common.result.Result;
import com.ichangzuo.common.result.ResultCode;
import com.ichangzuo.module.auth.service.AuthService;
import com.ichangzuo.common.constant.SecurityConstants;
import com.ichangzuo.common.enums.CaptchaTypeEnum;
import com.ichangzuo.system.model.dto.CaptchaResult;
import com.ichangzuo.system.model.dto.LoginResult;
import com.ichangzuo.config.property.CaptchaProperties;
import com.ichangzuo.core.security.util.JwtUtils;
import com.ichangzuo.system.service.UserService;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.awt.*;
import java.util.concurrent.TimeUnit;

/**
 * 认证服务实现类
 *
 * @author haoxr
 * @since 2.4.0
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class AuthServiceImpl implements AuthService {

    private final AuthenticationManager authenticationManager;
    private final RedisTemplate<String, Object> redisTemplate;
    private final CodeGenerator codeGenerator;
    private final Font captchaFont;
    private final CaptchaProperties captchaProperties;
    private final UserService userService;

    /**
     * 登录
     *
     * @param username 用户名
     * @param password 密码
     * @return 登录结果
     */
    @Override
    public LoginResult login(String username, String password) {
        // 创建认证令牌对象
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(username.toLowerCase().trim(), password);
        // 执行用户认证
        Authentication authentication = authenticationManager.authenticate(authenticationToken);
        // 认证成功后生成JWT令牌
        String accessToken = JwtUtils.createToken(authentication);
        // 将认证信息存入Security上下文，便于在AOP（如日志记录）中获取当前用户信息
        SecurityContextHolder.getContext().setAuthentication(authentication);

        userService.afterUserLogon();
        // 返回包含JWT令牌的登录结果
        return LoginResult.builder()
                .tokenType("Bearer")
                .accessToken(accessToken)
                .build();
    }

    /**
     * 注销
     */
    @Override
    public void logout() {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String token = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (StrUtil.isNotBlank(token) && token.startsWith(SecurityConstants.JWT_TOKEN_PREFIX)) {
            token = token.substring(SecurityConstants.JWT_TOKEN_PREFIX.length());
            // 解析Token以获取有效载荷（payload）
            JSONObject payloads = JWTUtil.parseToken(token).getPayloads();
            // 解析 Token 获取 jti(JWT ID) 和 exp(过期时间)
            String jti = payloads.getStr(JWTPayload.JWT_ID);
            Long expiration = payloads.getLong(JWTPayload.EXPIRES_AT); // 过期时间(秒)
            // 如果exp存在，则计算Token剩余有效时间
            if (expiration != null) {
                long currentTimeSeconds = System.currentTimeMillis() / 1000;
                if (expiration < currentTimeSeconds) {
                    // Token已过期，不再加入黑名单
                    return;
                }
                // 将Token的jti加入黑名单，并设置剩余有效时间，使其在过期后自动从黑名单移除
                long ttl = expiration - currentTimeSeconds;
                redisTemplate.opsForValue().set(SecurityConstants.BLACKLIST_TOKEN_PREFIX + jti, null, ttl, TimeUnit.SECONDS);
            } else {
                // 如果exp不存在，说明Token永不过期，则永久加入黑名单
                redisTemplate.opsForValue().set(SecurityConstants.BLACKLIST_TOKEN_PREFIX + jti, null);
            }
        }
        // 清空Spring Security上下文
        SecurityContextHolder.clearContext();
    }

    /**
     * 获取验证码
     *
     * @return 验证码
     */
    @Override
    public CaptchaResult getCaptcha() {

        String captchaType = captchaProperties.getType();
        int width = captchaProperties.getWidth();
        int height = captchaProperties.getHeight();
        int interfereCount = captchaProperties.getInterfereCount();
        int codeLength = captchaProperties.getCode().getLength();
        String codeType = captchaProperties.getCode().getType();

        AbstractCaptcha captcha;
        if (CaptchaTypeEnum.CIRCLE.name().equalsIgnoreCase(captchaType)) {
            captcha = CaptchaUtil.createCircleCaptcha(width, height, codeLength, interfereCount);
        } else if (CaptchaTypeEnum.GIF.name().equalsIgnoreCase(captchaType)) {
            captcha = CaptchaUtil.createGifCaptcha(width, height, codeLength);
        } else if (CaptchaTypeEnum.LINE.name().equalsIgnoreCase(captchaType)) {
            captcha = CaptchaUtil.createLineCaptcha(width, height, codeLength, interfereCount);
        } else if (CaptchaTypeEnum.SHEAR.name().equalsIgnoreCase(captchaType)) {
            captcha = CaptchaUtil.createShearCaptcha(width, height, codeLength, interfereCount);
        } else {
            throw new IllegalArgumentException("Invalid captcha type: " + captchaType);
        }

        captcha.setGenerator(codeGenerator);
        captcha.setTextAlpha(captchaProperties.getTextAlpha());
        captcha.setFont(captchaFont);

        String captchaCode = captcha.getCode();
        String imageBase64Data = captcha.getImageBase64Data();

        // 验证码文本缓存至Redis，用于登录校验
        String captchaKey = IdUtil.fastSimpleUUID();
        redisTemplate.opsForValue().set(SecurityConstants.CAPTCHA_CODE_PREFIX + captchaKey, captchaCode,
                captchaProperties.getExpireSeconds(), TimeUnit.SECONDS);

        return CaptchaResult.builder()
                .captchaKey(captchaKey)
                .captchaBase64(imageBase64Data)
                .build();
    }

    @Override
    public Result<?> register(String username, String password, String verifyCode){
        if(userService.existUsername(username))
            return Result.failed(ResultCode.USER_NAME_EXISTED);

        int verify = userService.checkVerificationCode(username, verifyCode, "注册新用户");
        if(verify==-1)
            return Result.failed(ResultCode.VERIFY_CODE_ERROR);
        else if(verify==-2)
            return Result.failed(ResultCode.VERIFY_CODE_TIMEOUT);
        else if(verify==-3)
            return Result.failed(ResultCode.PARAM_ERROR);

        if( !userService.register(username, password) )
            return Result.failed(ResultCode.SYSTEM_EXECUTION_ERROR);

        LoginResult result = login(username, password);
        if(result==null)
            return Result.failed(ResultCode.SYSTEM_EXECUTION_ERROR);

        userService.setNewAccount();

        return Result.success(result);
    }

    @Override
    public Result<?> sendRegCode(String contact){
        if(userService.existUsername(contact))
            return Result.failed(ResultCode.USER_NAME_EXISTED);

        String subject = "注册新用户";
        boolean result = userService.sendVerificationCode(contact, subject);
        return Result.judge(result);
    }

    @Override
    public Result<?> verifyRegCode(String username, String verifyCode) {
        int verify = userService.checkVerificationCode(username, verifyCode, "注册新用户");
        if(verify==-1)
            return Result.failed(ResultCode.VERIFY_CODE_ERROR);
        else if(verify==-2)
            return Result.failed(ResultCode.VERIFY_CODE_TIMEOUT);
        else if(verify==-3)
            return Result.failed(ResultCode.PARAM_ERROR);

        return Result.success();
    }

    @Override
    public Result<?> autoLogin(String username, String password){
        LoginResult result = login(username, password);
        if(result==null)
            return Result.failed(ResultCode.SYSTEM_EXECUTION_ERROR);

        return Result.success(result);
    }
}
