package cn.ey.biz.module.system.service.auth;

import cn.ey.biz.common.enums.UserTypeEnum;
import cn.ey.biz.common.exception.enums.GlobalErrorCodeConstants;
import cn.ey.biz.common.util.http.HttpUtils;
import cn.ey.biz.common.util.json.JsonUtils;
import cn.ey.biz.framework.third.auth.AuthRequestFactory;
import cn.ey.biz.framework.third.auth.config.JustAuthProperties;
import cn.ey.biz.framework.third.auth.oauth.config.AuthConfig;
import cn.ey.biz.framework.third.auth.oauth.model.AuthCallback;
import cn.ey.biz.framework.third.auth.oauth.model.AuthResponse;
import cn.ey.biz.framework.third.auth.oauth.model.AuthToken;
import cn.ey.biz.framework.third.auth.oauth.model.AuthUser;
import cn.ey.biz.framework.third.auth.oauth.request.AuthRequest;
import cn.ey.biz.framework.third.auth.oauth.utils.AuthStateUtils;
import cn.ey.biz.module.system.controller.admin.auth.vo.AuthLoginRespVO;
import cn.ey.biz.module.system.controller.admin.auth.vo.ThirdAuthCallBackReqVO;
import cn.ey.biz.module.system.controller.admin.auth.vo.ThirdAuthLoginReqVO;
import cn.ey.biz.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import cn.ey.biz.module.system.dal.dataobject.user.AdminUserDO;
import cn.ey.biz.module.system.dal.mapper.oauth2.OAuth2AccessTokenMapper;
import cn.ey.biz.module.system.enums.logger.LoginLogTypeEnum;
import cn.ey.biz.module.system.enums.logger.LoginResultEnum;
import cn.ey.biz.module.system.enums.oauth2.OAuth2ClientConstants;
import cn.ey.biz.module.system.enums.social.SocialTypeEnum;
import cn.ey.biz.module.system.service.oauth2.OAuth2TokenService;
import cn.ey.biz.module.system.service.user.AdminUserService;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.jwt.JWT;
import com.alibaba.fastjson.JSON;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

import java.net.URLEncoder;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static cn.ey.biz.common.exception.util.ServiceExceptionUtil.exception;
import static cn.ey.biz.common.exception.util.ServiceExceptionUtil.exception0;

/**
 * Third Auth Service 实现类
 *
 */
@Service
@Slf4j
public class ThirdAuthServiceImpl implements ThirdAuthService {

    @Resource
    private AuthRequestFactory authRequestFactory;
    @Resource
    private JustAuthProperties properties;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private AdminUserService userService;
    @Resource
    private OAuth2TokenService oauth2TokenService;
    @Resource
    private AdminAuthService adminAuthService;
    @Resource
    private OAuth2AccessTokenMapper oauth2AccessTokenMapper;

    @Override
    public AuthLoginRespVO login(Integer platform, ThirdAuthCallBackReqVO reqVo) {
        AuthUser user = this.callback(platform, reqVo.getCode(), reqVo.getState());
        // 获取 Token 信息
        AuthToken token = user.getToken();
        // 组装返回数据
        return new AuthLoginRespVO()
                .setIdToken(token.getIdToken())
                .setRefreshToken(token.getRefreshToken());
    }

    @Override
    public AuthLoginRespVO login02(ThirdAuthLoginReqVO reqVO) {
        // 从缓存获取 Token 信息
        AuthUser user = JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(reqVO.getCode()), AuthUser.class);
        if(user == null){
            throw exception(GlobalErrorCodeConstants.BAD_REQUEST);
        }
        // 打印用户信息
        log.info("user info:{}.", JSON.toJSONString(user));
        // 清除掉对应的 Token 信息
        stringRedisTemplate.delete(reqVO.getCode());
        // 获取 Token
        AuthToken token = user.getToken();

        // 用户是否存在
        AdminUserDOWrapper aud = this.getUserByToken(token.getIdToken());

        // 记录登录结果
        adminAuthService.createLoginLog(aud.adminUserDO.getId(),
                aud.adminUserDO.getUsername(),
                LoginLogTypeEnum.LOGIN_AAD,
                LoginResultEnum.SUCCESS
        );

        // 组装响应
        return new AuthLoginRespVO()
                .setAccessToken(token.getIdToken())
                .setIdToken(token.getIdToken())
                .setRefreshToken(token.getRefreshToken());
    }

    @Override
    public AuthLoginRespVO login03(ThirdAuthLoginReqVO reqVO) {
        // 从缓存获取 Token 信息
        AuthUser user = JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(reqVO.getCode()), AuthUser.class);
        if(user == null){
            log.error("auth user not exist:{}.", reqVO.getCode());
            throw exception(GlobalErrorCodeConstants.BAD_REQUEST);
        }
        // 打印用户信息
        log.info("auth user info:{}.", JSON.toJSONString(user));
        // 清除掉对应的 Token 信息
        stringRedisTemplate.delete(reqVO.getCode());

        // 校验账号是否存在
        AdminUserDOWrapper aud = this.getUserByToken(user.getToken().getIdToken());

        // 是否开启强制下线
        if(properties.isForceLogout()) {
            // 如果已经登录，则强制下线
            List<OAuth2AccessTokenDO> oauth2AccessTokenDO = oauth2AccessTokenMapper.selectByUserId(aud.adminUserDO.getId());
            if (oauth2AccessTokenDO != null) {
                oauth2AccessTokenDO.forEach(item -> {
                    adminAuthService.logout(item.getAccessToken(), LoginLogTypeEnum.LOGOUT_DELETE.getType());
                });
            }
        }

        // 记录登录结果
        adminAuthService.createLoginLog(aud.adminUserDO.getId(),
                aud.adminUserDO.getUsername(),
                LoginLogTypeEnum.LOGIN_AAD_TOKEN,
                LoginResultEnum.SUCCESS
        );

        // 创建访问令牌
        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(aud.adminUserDO.getId(),
                UserTypeEnum.ADMIN.getValue(),
                OAuth2ClientConstants.CLIENT_ID_DEFAULT,
                aud.getGroups());

        // 组装响应
        return new AuthLoginRespVO()
                .setAccessToken(accessTokenDO.getAccessToken())
                .setIdToken(accessTokenDO.getAccessToken())
                .setRefreshToken(accessTokenDO.getRefreshToken());
    }

    @SneakyThrows
    @Override
    public String logout(Integer platform, String accessToken) {
        // 如果是 Token 置换的场景，首先删除
        if(properties.isDisplace()){
            adminAuthService.logout(accessToken, LoginLogTypeEnum.LOGOUT_SELF.getType());
        }else{
            AdminUserDOWrapper aud = this.getUserByToken(accessToken);
            // 记录登出结果
            adminAuthService.createLoginLog(aud.adminUserDO.getId(),
                    aud.adminUserDO.getUsername(),
                    LoginLogTypeEnum.LOGOUT_SELF,
                    LoginResultEnum.SUCCESS
            );
        }
        String source = SocialTypeEnum.valueOfType(platform).getSource();
        AuthConfig config = properties.getType().get(source);
        String logoutRedirectUri = config.getLogoutRedirectUri();
        if(StrUtil.isBlank(logoutRedirectUri)){
            return config.getLogoutUri();
        }
        return config.getLogoutUri() + "?post_logout_redirect_uri=" + URLEncoder.encode(logoutRedirectUri, "UTF-8");
    }

    @Override
    public AuthLoginRespVO refreshToken(Integer platform, String refreshToken) {
        // 获得对应的 AuthRequest 实现
        AuthRequest authRequest = authRequestFactory.get(SocialTypeEnum.valueOfType(platform).getSource());
        // 获取 RefreshToken
        AuthResponse<AuthToken> response = authRequest.refresh(AuthToken.builder().refreshToken(refreshToken).build());
        if(response.ok()){
            AuthToken token = response.getData();
            AuthLoginRespVO result = new AuthLoginRespVO()
                    .setAccessToken(token.getIdToken())
                    .setIdToken(token.getIdToken())
                    .setRefreshToken(token.getRefreshToken());
            return result;
        }else{
            throw exception(GlobalErrorCodeConstants.BAD_REQUEST);
        }
    }

    @Override
    public String getAuthorizeUrl(Integer platform, String redirectUri, String app) {
        // 获得对应的 AuthRequest 实现
        String source = SocialTypeEnum.valueOfType(platform).getSource();
        AuthRequest authRequest = authRequestFactory.get(source);
        // 生成跳转地址
        if(StrUtil.isEmpty(redirectUri)){
            redirectUri = properties.getType().get(source).getRedirectUri();
        }
        String authorizeUri = authRequest.authorize(app+StrUtil.DASHED+AuthStateUtils.createState());
        return HttpUtils.replaceUrlQuery(authorizeUri, "redirect_uri", redirectUri);
    }

    @Override
    public AuthUser callback(Integer platform, String code, String state) {
        // 获得对应的 AuthRequest 实现
        AuthRequest authRequest = authRequestFactory.get(SocialTypeEnum.valueOfType(platform).getSource());
        // 开始登录
        AuthCallback callback = new AuthCallback().builder()
                .code(code)
                .state(state)
                .build();
        AuthResponse<AuthUser> response = authRequest.login(callback);
        if(response.ok()){
            // 处理用户信息
            return response.getData();
        }else{
            throw exception(GlobalErrorCodeConstants.BAD_REQUEST);
        }
    }

    @Override
    public String callback02(Integer platform, String code, String state) {
        // 获取认证用户
        AuthUser user = this.callback(platform, code, state);
        // 生成缓存 Key
        String uuid = IdUtil.fastSimpleUUID();
        // 默认缓存一分钟
        stringRedisTemplate.opsForValue().set(code, JsonUtils.toJsonString(user), 1000*60*2, TimeUnit.MILLISECONDS);
        return uuid;
    }

    /**
     * 根据 AAD Token 解析参数信息
     *
     * @param token
     * @return
     */
    private AdminUserDOWrapper getUserByToken(String token){
        JWT jwt = JWT.of(token);
        String gpn = jwt.getPayloads().get(properties.getGpnKey(), String.class);
        AdminUserDO aud = userService.selectByUserCode(gpn);
        if(aud == null){
            log.error("admin user not exist:{}.", gpn);
            throw exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), "用户不存在:{}.", gpn);
        }
        List<String> groups = jwt.getPayloads().get(properties.getGroupKey(), List.class);

        // 包装
        AdminUserDOWrapper wrapper = new AdminUserDOWrapper();
        wrapper.setAdminUserDO(aud);
        wrapper.setGroups(groups);
        return wrapper;
    }

    @Data
    static class AdminUserDOWrapper{
        AdminUserDO adminUserDO;
        List<String> groups;
    }

}
