/*
 * Copyright 2019-2029 geekidea(https://github.com/geekidea)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.wecloud.im.core.shiro.jwt;

import com.wecloud.im.core.common.api.ApiCode;
import com.wecloud.im.core.common.api.ApiResult;
import com.wecloud.im.core.shiro.service.ShiroLoginService;
import com.wecloud.im.core.shiro.signature.Application;
import com.wecloud.im.core.shiro.signature.ApplicationService;
import com.wecloud.im.core.shiro.signature.SignatureAuthToken;
import com.wecloud.im.core.shiro.signature.SignatureChecker;
import com.wecloud.im.core.shiro.util.JwtTokenUtil;
import com.wecloud.im.core.shiro.util.JwtUtil;
import com.wecloud.im.core.util.HttpServletResponseUtil;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.util.WebUtils;

/**
 * Shiro JWT授权过滤器
 *
 * @author geekidea
 * @date 2019-09-27
 * @since 1.3.0.RELEASE
 **/
@Slf4j
public class SecurityFilter extends AuthenticatingFilter {

    private final ShiroLoginService shiroLoginService;

    private static final String AUTHORIZATION = "Authorization";

    private ApplicationService applicationService;

    public SecurityFilter(ShiroLoginService shiroLoginService, ApplicationService applicationService) {
        this.shiroLoginService = shiroLoginService;
        this.applicationService = applicationService;
    }

    /**
     * 将JWT Token包装成AuthenticationToken
     * 从http请求头中取得token,并从redis中取出token相关信息
     *
     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) {

        // 从请求头中取出x-im-from
        ShiroHttpServletRequest ShiroHttpServletRequest = (ShiroHttpServletRequest) servletRequest;

        HttpServletRequest request =
                (HttpServletRequest) ShiroHttpServletRequest.getRequest();
        String from = request.getHeader("x-im-from");
        if ("server".equals(from)) {
            // 服务端发起的请求，校验签名
            String authorization = request.getHeader(AUTHORIZATION);
            if (StringUtils.isBlank(authorization)) {
                throw new AuthenticationException("token不能为空");
            }
            // 校验Signature是否完整 合规
            if (!authorization.startsWith("WECLOUD-IM AppKey")) {
                throw new AuthenticationException("token错误");
            }
            if (!authorization.contains(", Signature:")) {
                throw new AuthenticationException("token 未包含签名信息");
            }

            String appKey = getAppKeyFromAuthorization(authorization);
            String clientSignature = getSignatureFromAuthorization(authorization);

            // 校验通过 获取并校验Application信息
            Application application = applicationService.getApplication(appKey);
            if (application == null) {
                throw new AuthenticationException("application does not exist");
            }

            if (!application.isActive()) {
                throw new AuthenticationException("application is unavailable");
            }
            Long appId = application.getId();
            String appSecret = application.getAppSecret();
            SignatureChecker.check(request, clientSignature, appSecret);

            // 构建SignatureAuthToken
            return new SignatureAuthToken(appKey, appId);
        } else {
            // 客户端发起的请求，校验token
            // 从http请求头中取得token
            String token = JwtTokenUtil.getToken();
            if (StringUtils.isBlank(token)) {
                throw new AuthenticationException("token不能为空");
            }
            if (JwtUtil.isExpired(token)) {
                throw new AuthenticationException("JWT Token已过期,token:" + token);
            }

            // 从redis 获取jwt数据
            JwtToken jwtToken = shiroLoginService.getJwtTokenForRedis(token);
            if (jwtToken == null) {
                throw new AuthenticationException("Redis Token不存在,token:" + token);
            }
            return JwtToken.build(token, jwtToken.getSalt(), jwtToken.getExpireSecond(), jwtToken.getClientId(), jwtToken.getAppKey(), jwtToken.getPlatform());
        }


    }


    /**
     * 访问失败处理
     *
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        log.error("访问被拒绝。。。");
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        // 返回401
        httpServletResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        // 设置响应码为401或者直接输出消息
        String url = httpServletRequest.getRequestURI();
        log.error("onAccessDenied url：{}", url);
        ApiResult<Boolean> apiResult = ApiResult.fail(ApiCode.UNAUTHORIZED);
        HttpServletResponseUtil.printJson(httpServletResponse, apiResult);
        return false;
    }

    /**
     * 执行登陆认证
     * 判断是否允许访问
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        String url = WebUtils.toHttp(request).getRequestURI();
        log.info("isAccessAllowed url:{}", url);
        if (this.isLoginRequest(request, response)) {
            return true;
        }
        boolean allowed = false;
        try {
            allowed = executeLogin(request, response);
        } catch (IllegalStateException e) { //not found any token
            log.error("Token不能为空", e);
        } catch (Exception e) {
            log.error("访问错误", e);
        }
        return allowed || super.isPermissive(mappedValue);
    }

    /**
     * 登录成功处理
     *
     * @param token
     * @param subject
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception {
        String url = WebUtils.toHttp(request).getRequestURI();
        log.info("鉴权成功,token:{},url:{}", token, url);
        if (token instanceof JwtToken) {
            // 刷新token
            JwtToken jwtToken = (JwtToken) token;
            HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        }
//        shiroLoginService.refreshToken(jwtToken, httpServletResponse);
        return true;
    }

    /**
     * 登录失败处理
     *
     * @param token
     * @param e
     * @param request
     * @param response
     * @return
     */
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        log.error("登录失败，token:" + token + ",error:" + e.getMessage(), e);
        return false;
    }

    private String getAppKeyFromAuthorization(String authorization) {
        // 处理token
        String[] tokenInfo = authorization.split(",");
        // appkey 格式是固定的，直接替换
        return tokenInfo[0].replace("WECLOUD-IM AppKey:", "").trim();
    }

    private String getSignatureFromAuthorization(String authorization) {
        // 处理token
        String[] tokenInfo = authorization.split(",");
        // 客户端传入的签名信息
        return tokenInfo[1].replace("Signature:", "").trim();
    }
}
