package com.njcn.auth.controller;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONObject;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.njcn.auth.service.UserTokenService;
import com.njcn.auth.utils.AuthPubUtil;
import com.njcn.common.pojo.annotation.OperateInfo;
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.redis.utils.RedisUtil;
import com.njcn.user.api.PassWordRuleFeugnClient;
import com.njcn.user.api.UserFeignClient;
import com.njcn.user.enums.UserResponseEnum;
import com.njcn.user.pojo.po.UserStrategy;
import com.njcn.web.controller.BaseController;
import com.njcn.web.utils.RequestUtil;
import com.njcn.web.utils.RestTemplateUtil;
import com.sgcc.epri.auth.session.HttpSessionManager;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
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.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URI;
import java.security.KeyPair;
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")
@RequiredArgsConstructor
public class AuthController extends BaseController {


    private final TokenEndpoint tokenEndpoint;

    private final KeyPair keyPair;

    private final RedisUtil redisUtil;

    private final UserFeignClient userFeignClient;

    private final PassWordRuleFeugnClient passWordRuleFeugnClient;

    private final UserTokenService userTokenService;

    @Value("${cas.redirect-url:http://10.21.30.11:8088/#/login?flag=1}")
    private String redirectUrl;

    private String UsernamePrefix = "CAS_";


    @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);
        }

        //判断当前登录是否超过最大并发数
        UserStrategy data = passWordRuleFeugnClient.getUserStrategy().getData();
        String onlineUserKey = SecurityConstants.TOKEN_ONLINE_PREFIX;
        List<UserTokenInfo> onLineUser = (List<UserTokenInfo>) redisUtil.getLikeListAllValues(onlineUserKey);
        if (CollectionUtil.isNotEmpty(onLineUser)) {
            String finalUsername = username;
            onLineUser = onLineUser.stream().filter(item -> {
                JSONObject jsonObject = AuthPubUtil.getLoginByToken(item.getRefreshToken());
                String login = jsonObject.getStr(SecurityConstants.USER_NAME_KEY);
                long exp = Long.parseLong(jsonObject.getStr(SecurityConstants.JWT_EXP));
                long now = Calendar.getInstance().getTimeInMillis() / 1000;
                return (exp > now) && !login.equals(finalUsername);
            }).collect(Collectors.toList());
        }

        Integer maxNum = data.getMaxNum();
        if ((CollectionUtil.isNotEmpty(onLineUser) ? onLineUser.size() : 0) >= maxNum) {
            throw new BusinessException(UserResponseEnum.LOGIN_USER_OVERLIMIT);
        }


        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)) {
            userFeignClient.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(HttpServletRequest request, HttpServletResponse response) {
        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);
        }


        // 以下代码是辽宁登出代码，关键：使 Session 失效
        request.getSession().invalidate();

        // 清除 JSESSIONID
        Cookie jsessionidCookie = new Cookie("JSESSIONID", null);
        jsessionidCookie.setMaxAge(0);
        jsessionidCookie.setPath("/");
        response.addCookie(jsessionidCookie);

        // 清除 loginUser Cookie（关键！）
        Cookie loginUserCookie = new Cookie("loginUser", null);
        loginUserCookie.setMaxAge(0);
        loginUserCookie.setPath("/");
        response.addCookie(loginUserCookie);

        log.info("登出成功。。。。。。。。。。。。。。。。");
        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:20214/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);
    }


    /**
     * 【电科院CAS调控云单点登录适配】
     * 这个只用来匹配
     */
    @ApiIgnore
    @GetMapping("/lnLogin")
    @ApiOperation("获取ln系统用户token")
    public HttpResult<Object> lnLogin(@RequestParam String clientId, @RequestParam String clientSecret, HttpServletRequest request, HttpServletResponse response) throws HttpRequestMethodNotSupportedException {
        log.info("进入lnLogin++++++++++++++++++");
        String methodDescribe = getMethodDescribe("lnLogin");

        // 读取CAS信息
        String userName = String.valueOf(HttpSessionManager.getAttribute(request, HttpSessionManager.AUTH_USER_KEY));
        String userId = String.valueOf(HttpSessionManager.getAttribute(request, HttpSessionManager.USER_ID_KEY));
        String owner = String.valueOf(HttpSessionManager.getAttribute(request, HttpSessionManager.USER_OWNER));
        String name = String.valueOf(HttpSessionManager.getAttribute(request, HttpSessionManager.USER_NAME_CHN));
        String employeeId = String.valueOf(HttpSessionManager.getAttribute(request, HttpSessionManager.USER_EMPLOYEE_ID));

        log.info("userName:{}", userName);
        log.info("userId:{}", userId);
        log.info("owner:{}", owner);
        log.info("name:{}", name);
        log.info("employeeId:{}", employeeId);

        if ("null".equals(userName)) {
            throw new BusinessException(UserResponseEnum.LN_AUTH_ERROR);
        }

        // 2. 【关键】用户名前面加上"CAS_"前缀，让UserDetailsService识别
        String casUsername = userName;

        // 2. 直接构造 OAuth2 必要参数（跳过所有密码/加密校验）
        Map<String, String> parameters = new HashMap<>();
        parameters.put("grant_type", "password");          // 固定密码模式
        parameters.put("client_id", clientId);               // 你的客户端ID
        parameters.put("client_secret", clientSecret);        // 你的客户端秘钥
        parameters.put("username", userName);              // 统一认证传过来的用户名
        parameters.put("password", "@#001njcnpqs");

        // 3. 直接调用 OAuth2 生成 Token（跳过所有登录校验）
        Authentication authentication = new UsernamePasswordAuthenticationToken(
                clientId, clientSecret, Collections.emptyList()
        );

        OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(authentication, parameters).getBody();


        // 获取过期时间（秒数）
        int expiresIn = oAuth2AccessToken.getExpiresIn();
        log.info("token过期时间: {} 秒", expiresIn);
        log.info("token过期时间: {} 分钟", expiresIn / 60);
        log.info("token过期时间: {} 小时", expiresIn / 3600);
        log.info("====== 免密登录成功，返回token给前端 ======");
        return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, oAuth2AccessToken, methodDescribe);
    }


    /**
     * 重点：
     * 这个接口 不加 白名单
     * 访问它 → 自动跳CAS → 登录成功 → 重定向到登录页
     */
    @GetMapping("/lnCheck")
    @ApiOperation("检查CAS是否登录")
    public void lnToken(HttpServletRequest request, HttpServletResponse response) throws IOException {
        log.info("进入lnCheck。。。。");
        response.sendRedirect(redirectUrl);
    }


    /**
     */
    @GetMapping("/lnRefreshToken")
    @ApiOperation("刷新token")
    public HttpResult<Object> lnRefreshToken(
            @RequestParam String refreshToken,
            @RequestParam String clientId,
            @RequestParam String clientSecret,
            HttpServletRequest request,
            HttpServletResponse response) throws HttpRequestMethodNotSupportedException {

        log.info("进入lnRefreshToken，开始刷新token");
        String methodDescribe = getMethodDescribe("lnRefreshToken");

        // ========== 【前置：优先校验CAS会话是否过期】 ==========
        String userName = String.valueOf(HttpSessionManager.getAttribute(request, HttpSessionManager.AUTH_USER_KEY));
        if ("null".equals(userName)) {
            log.error("CAS会话已过期，跳转登录页");
            throw new BusinessException(UserResponseEnum.LN_AUTH_ERROR);
        }

        // 1. 先尝试用refresh_token正常刷新
        Map<String, String> parameters = new HashMap<>();
        parameters.put("grant_type", "refresh_token");
        parameters.put("refresh_token", refreshToken);
        parameters.put("client_id", clientId);
        parameters.put("client_secret", clientSecret);

        Authentication authentication = new UsernamePasswordAuthenticationToken(
                clientId, clientSecret, Collections.emptyList()
        );

        try {
            OAuth2AccessToken newAccessToken = tokenEndpoint.postAccessToken(authentication, parameters).getBody();
            log.info("refresh_token刷新成功");
            return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, newAccessToken, methodDescribe);
        } catch (Exception e) {
            log.warn("refresh_token刷新失败，尝试回退到CAS会话重新签发token", e);
        }


        // 3. CAS会话有效，重新签发token（等同于重新登录）
        log.info("CAS会话有效，为用户[{}]重新签发token", userName);
        String casUsername = userName;
        Map<String, String> reLoginParams = new HashMap<>();
        reLoginParams.put("grant_type", "password");
        reLoginParams.put("client_id", clientId);
        reLoginParams.put("client_secret", clientSecret);
        reLoginParams.put("username", userName);
        reLoginParams.put("password", "@#001njcnpqs");

        Authentication reAuth = new UsernamePasswordAuthenticationToken(
                clientId, clientSecret, Collections.emptyList()
        );

        OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(reAuth, reLoginParams).getBody();
        log.info("CAS回退重签token成功，userName:{}", userName);
        return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, oAuth2AccessToken, methodDescribe);
    }
}
