package com.njcn.product.auth.controller;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;

import com.njcn.common.pojo.annotation.OperateInfo;
import com.njcn.common.pojo.constant.LogInfo;
import com.njcn.common.pojo.constant.OperateType;
import com.njcn.common.pojo.constant.SecurityConstants;
import com.njcn.common.pojo.dto.UserTokenInfo;
import com.njcn.common.pojo.enums.common.LogEnum;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.common.utils.HttpResultUtil;
import com.njcn.common.utils.LogUtil;
import com.njcn.common.utils.sm.DesUtils;
import com.njcn.common.utils.sm.Sm2;
import com.njcn.product.auth.pojo.enums.UserResponseEnum;
import com.njcn.product.auth.service.IUserService;
import com.njcn.product.auth.service.UserTokenService;
import com.njcn.product.auth.utils.AuthPubUtil;
import com.njcn.redis.utils.RedisUtil;

import com.njcn.web.controller.BaseController;
import com.njcn.web.utils.RequestUtil;
import com.njcn.web.utils.RestTemplateUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.UriComponentsBuilder;
import springfox.documentation.annotations.ApiIgnore;

import javax.servlet.http.HttpServletRequest;
import java.net.URI;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.interfaces.RSAPublicKey;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author hongawen
 */
@Api(tags = "认证中心")
@Slf4j
@RestController
@RequestMapping("/oauth")
@AllArgsConstructor
public class AuthController extends BaseController {


    private final TokenEndpoint tokenEndpoint;

    private final KeyPair keyPair;

    private final RedisUtil redisUtil;

    private final IUserService userService;


    private final UserTokenService userTokenService;


    @ApiIgnore
    @OperateInfo(info = LogEnum.SYSTEM_SERIOUS, operateType = OperateType.AUTHENTICATE)
    @ApiOperation("登录认证")
    @ApiImplicitParams({
            @ApiImplicitParam(name = SecurityConstants.GRANT_TYPE, defaultValue = "password", value = "授权模式", required = true),
            @ApiImplicitParam(name = SecurityConstants.CLIENT_ID, defaultValue = "njcn", value = "Oauth2客户端ID", required = true),
            @ApiImplicitParam(name = SecurityConstants.CLIENT_SECRET, defaultValue = "njcnpqs", value = "Oauth2客户端秘钥", required = true),
            @ApiImplicitParam(name = SecurityConstants.REFRESH_TOKEN, value = "刷新token"),
            @ApiImplicitParam(name = SecurityConstants.USERNAME, value = "登录用户名"),
            @ApiImplicitParam(name = SecurityConstants.PASSWORD, value = "登录密码"),
            @ApiImplicitParam(name = SecurityConstants.IMAGE_CODE, value = "图形验证码"),
            @ApiImplicitParam(name = SecurityConstants.PHONE, value = "手机号"),
            @ApiImplicitParam(name = SecurityConstants.SMS_CODE, value = "短信验证码"),
    })
    @PostMapping("/token")
    public Object postAccessToken(@ApiIgnore Principal principal, @RequestParam @ApiIgnore Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
        String methodDescribe = getMethodDescribe("postAccessToken");
        String username = parameters.get(SecurityConstants.USERNAME);


        String grantType = parameters.get(SecurityConstants.GRANT_TYPE);
        if (grantType.equalsIgnoreCase(SecurityConstants.GRANT_CAPTCHA) || grantType.equalsIgnoreCase(SecurityConstants.REFRESH_TOKEN_KEY)) {
            username = DesUtils.aesDecrypt(username);
        } else if (grantType.equalsIgnoreCase(SecurityConstants.GRANT_SMS_CODE)) {
            //短信方式登录，将手机号赋值为用户名
            username = parameters.get(SecurityConstants.PHONE);
        }



        if (grantType.equalsIgnoreCase(SecurityConstants.REFRESH_TOKEN_KEY)) {
            //如果是刷新token，需要去黑名单校验
            userTokenService.judgeRefreshToken(parameters.get(SecurityConstants.REFRESH_TOKEN_KEY));
        }
        RequestUtil.saveLoginName(username);
        OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
        //用户的登录名&密码校验成功后，判断当前该用户是否可以正常使用系统
        if (!grantType.equalsIgnoreCase(SecurityConstants.GRANT_SMS_CODE)) {
            userService.judgeUserStatus(username);
        }
        //登录成功后，记录token信息，并处理踢人效果
        userTokenService.recordUserInfo(oAuth2AccessToken, RequestUtil.getRealIp());
        if (!grantType.equalsIgnoreCase(SecurityConstants.PASSWORD)) {
            return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, oAuth2AccessToken, methodDescribe);
        } else {
            return oAuth2AccessToken;
        }
    }

    @OperateInfo(info = LogEnum.SYSTEM_SERIOUS, operateType = OperateType.LOGOUT)
    @ApiOperation("用户登出系统")
    @DeleteMapping("/logout")
    public HttpResult<Object> logout() {
        String methodDescribe = getMethodDescribe("logout");
        String userIndex = RequestUtil.getUserIndex();
        String username = RequestUtil.getUsername();
        LogUtil.njcnDebug(log, "{}，用户名为：{}", methodDescribe, username);
        String blackUserKey = SecurityConstants.TOKEN_BLACKLIST_PREFIX + userIndex;
        String onlineUserKey = SecurityConstants.TOKEN_ONLINE_PREFIX + userIndex;
        Object onlineTokenInfoOld = redisUtil.getObjectByKey(onlineUserKey);
        List<UserTokenInfo> blackUsers = (List<UserTokenInfo>) redisUtil.getObjectByKey(blackUserKey);
        UserTokenInfo userTokenInfo;
        if (!Objects.isNull(onlineTokenInfoOld)) {
            //清除在线token信息
            redisUtil.delete(onlineUserKey);
            userTokenInfo = (UserTokenInfo) onlineTokenInfoOld;
            if (CollectionUtils.isEmpty(blackUsers)) {
                blackUsers = new ArrayList<>();
            }
            blackUsers.add(userTokenInfo);
            LocalDateTime refreshTokenExpire = userTokenInfo.getRefreshTokenExpire();
            long lifeTime = Math.abs(refreshTokenExpire.plusMinutes(5L).toEpochSecond(ZoneOffset.of("+8")) - LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8")));
            redisUtil.saveByKeyWithExpire(blackUserKey, blackUsers, lifeTime);
        }
        return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, null, methodDescribe);
    }


    /**
     * 文档隐藏该接口
     */
    @ApiIgnore
    @ApiOperation("RSA公钥获取接口")
    @GetMapping("/getPublicKey")
    public Map<String, Object> getPublicKey() {
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAKey key = new RSAKey.Builder(publicKey).build();
        return new JWKSet(key).toJSONObject();
    }

    /**
     * 自动登录
     */
    @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.AUTHENTICATE)
    @ApiOperation("自动登录")
    @PostMapping("/autoLogin")
    @ApiImplicitParam(name = "phone", value = "手机号", required = true, paramType = "query")
    @ApiIgnore
    public HttpResult<Object> autoLogin(@RequestParam String phone) {
        String methodDescribe = getMethodDescribe("autoLogin");
        String userUrl = "http://127.0.0.1:10214/oauth/token";
        UriComponentsBuilder builder = UriComponentsBuilder.fromHttpUrl(userUrl)
                .queryParam("grant_type", "sms_code")
                .queryParam("client_id", "njcnapp")
                .queryParam("client_secret", "njcnpqs")
                .queryParam("phone", phone)
                .queryParam("smsCode", "123456789");
        URI uri = builder.build().encode().toUri();
        return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, Objects.requireNonNull(RestTemplateUtil.post(uri, HttpResult.class).getBody()).getData(), methodDescribe);
    }

    @OperateInfo(info = LogEnum.SYSTEM_COMMON)
    @GetMapping("/generateSm2Key")
    @ApiOperation("根据登录名获取公钥")
    @ApiImplicitParam(name = "loginName", value = "登录名", required = true)
    public HttpResult<String> generateSm2Key(String loginName, @ApiIgnore HttpServletRequest request) {
        System.out.println("request1==:" + request);
        if (StrUtil.isBlankIfStr(loginName)) {
            RequestUtil.saveLoginName(LogInfo.UNKNOWN_USER);
            throw new BusinessException(UserResponseEnum.LOGIN_USERNAME_INVALID);
        }
        String methodDescribe = getMethodDescribe("generateSm2Key");
        loginName = DesUtils.aesDecrypt(loginName);
        RequestUtil.saveLoginName(loginName);
        Map<String, String> keyMap;
        try {
            keyMap = Sm2.getSm2CipHer();
        } catch (NoSuchAlgorithmException e) {
            throw new BusinessException(CommonResponseEnum.SM2_CIPHER_ERROR);
        }
        String publicKey = keyMap.get("publicKey");
        String privateKey = keyMap.get("privateKey");
        String ip = request.getHeader(SecurityConstants.REQUEST_HEADER_KEY_CLIENT_REAL_IP);

        if (redisUtil.hasKey(loginName + ip)) {
            //秘钥先删除再添加
            redisUtil.delete(loginName + ip);
        }
        // 保存私钥到 redis
        redisUtil.saveByKeyWithExpire(loginName + ip, privateKey, 5 * 60L);
        return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, publicKey, methodDescribe);
    }

}
