package com.dcit.authority.shiro.filter;

import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

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

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.StringUtils;

import com.alibaba.fastjson.JSON;
import com.dcit.authority.config.JwtConfig;
import com.dcit.authority.dto.JwtDTO;
import com.dcit.authority.service.SysUserService;
import com.dcit.authority.shiro.token.JwtToken;
import com.dcit.authority.utils.JsonWebTokenUtil;
import com.dcit.authority.utils.RequestResponseUtil;
import com.dcit.common.enums.ResultEnum;
import com.dcit.common.utils.ResultVOUtil;
import com.dcit.logging.util.IpUtil;

import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;

/**
* <p>Title: JwtFilter</p>
* <p>Description: </p>
* <p>Company: SJZ DC-IT co. LTD</p> 
* @author ZZZ
* @date 2018年9月6日 下午9:34:10
*/
@Slf4j
public class JwtFilter extends CustomizedPathMatchingFilter {
	
    private StringRedisTemplate stringRedisTemplate;
    
    private SysUserService sysUserService;
    
    private JwtConfig jwtConfig;
    
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object mappedValue) throws Exception {
        Subject subject = getSubject(servletRequest,servletResponse);

        //记录调用api日志到数据库

        boolean isJwtPost = (null != subject && !subject.isAuthenticated()) && isJwtSubmission(servletRequest);
        // 判断是否为JWT认证请求
        if (isJwtPost) {
            AuthenticationToken token = createJwtToken(servletRequest);
            try {
                subject.login(token);
                if(checkRoles(subject,mappedValue)){
                	
                	String jwt = ((HttpServletRequest)servletRequest).getHeader(JsonWebTokenUtil.TOKEN_HEADRE_KEY);//RequestResponseUtil.getHeader(servletRequest,JsonWebTokenUtil.TOKEN_HEADRE_KEY);
					JwtDTO jwtDTO = JsonWebTokenUtil.parseJwt(jwt, JsonWebTokenUtil.SECRET_KEY);
					
					String refreshJwt = stringRedisTemplate.opsForValue().get(JsonWebTokenUtil.REDIS_PREFIX + jwtDTO.getAccount());
					
					log.info("====refreshJwt====" + refreshJwt);
					
					//jwt只能使用一次，就失效，每次请求都返回新的jwt，模仿session
					//if (null != refreshJwt && refreshJwt.equals(jwt)){
					//jwt可以多次使用，在有效期内都可使用,每次请求都返回新的jwt
					if (null != refreshJwt){
						
						// 重新申请新的JWT
                        // 根据account获取其对应所拥有的角色(这里设计为角色对应资源，没有权限对应资源)
						// 每次请求都查询库不太合理 ？？？？
                        // String roles = sysUserService.loadAccountRole(jwtDTO.getAccount());
						
						String newJwt = JsonWebTokenUtil.issueJWT(jwtDTO.getTokenId().toString(), jwtDTO.getUid(), jwtDTO.getAccount(),
								jwtDTO.getIssuer(), jwtConfig.getPeriod(), jwtDTO.getRoles(), null, SignatureAlgorithm.HS256);
						
						log.info("====newJwt====" + newJwt);
						
						// 将签发的JWT存储到Redis： {JWT-SESSION-{appID} , jwt}
						stringRedisTemplate.opsForValue().set(JsonWebTokenUtil.REDIS_PREFIX + jwtDTO.getAccount(), newJwt, jwtConfig.getPeriod(), TimeUnit.HOURS);
						
						//新的jwt返回
						HttpServletResponse httpResponse = getAsHttpResponse(servletResponse);
						httpResponse.setHeader(JsonWebTokenUtil.TOKEN_HEADRE_KEY, newJwt);
						
						return true;
					}else{
						// 其他的判断为JWT错误无效
						log.error("JWT错误无效");
		                RequestResponseUtil.responseWrite(JSON.toJSONString(ResultVOUtil.error(ResultEnum.ERROR_JWT)),servletResponse);
						return false;
					}
                	
					/*HttpServletResponse httpResponse = getAsHttpResponse(servletResponse);
					httpResponse.setHeader(JsonWebTokenUtil.TOKEN_HEADRE_KEY, jwt);
					
					return true;*/
					
                }
                
                return false;
                
            }catch (AuthenticationException e) {

                // 其他的判断为JWT错误无效
				log.error("JWT错误无效--{}",e.getMessage());
                RequestResponseUtil.responseWrite(JSON.toJSONString(ResultVOUtil.error(ResultEnum.ERROR_JWT)),servletResponse);
        			
        		return false;

            }catch (Exception e) {
                // 其他错误
                log.error(IpUtil.getIpFromRequest(WebUtils.toHttp(servletRequest))+"--JWT认证失败"+e.getMessage(),e);
                // 告知客户端JWT错误1005,需重新登录申请jwt
                RequestResponseUtil.responseWrite(JSON.toJSONString(ResultVOUtil.error(ResultEnum.ERROR_JWT)),servletResponse);
                return false;
            }
        }else {
            // 请求未携带jwt 判断为无效请求
        	log.error("未携带jwt");
            RequestResponseUtil.responseWrite(JSON.toJSONString(ResultVOUtil.error(ResultEnum.ERROR_JWT)),servletResponse);
            return false;
        }
    }

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
    	Subject subject = getSubject(servletRequest,servletResponse);

        // 未认证的情况
        if (null == subject || !subject.isAuthenticated()) {
            // 告知客户端JWT认证失败需跳转到登录页面
            RequestResponseUtil.responseWrite(JSON.toJSONString(ResultVOUtil.error(ResultEnum.ERROR_JWT)),servletResponse);
        }else {
            // 已经认证但未授权的情况
            // 告知客户端JWT没有权限访问此资源
            RequestResponseUtil.responseWrite(JSON.toJSONString(ResultVOUtil.error(ResultEnum.ERROR_PERMISSION)),servletResponse);
        }
        // 过滤链终止
        return false;
    }

	private boolean isJwtSubmission(ServletRequest request) {

        String jwt = ((HttpServletRequest)request).getHeader(JsonWebTokenUtil.TOKEN_HEADRE_KEY);//RequestResponseUtil.getHeader(request,JsonWebTokenUtil.TOKEN_HEADRE_KEY);
        return (request instanceof HttpServletRequest) && !StringUtils.isEmpty(jwt);
        
    }

    private AuthenticationToken createJwtToken(ServletRequest request) {
        
        Map<String,String> maps = RequestResponseUtil.getRequestHeaders(request);
        String ipHost = request.getRemoteAddr();
        String jwt = ((HttpServletRequest)request).getHeader(JsonWebTokenUtil.TOKEN_HEADRE_KEY);
        String deviceInfo = maps.get("deviceInfo");

        return new JwtToken(null,ipHost,deviceInfo,jwt);

    }

    /**
     * 验证当前用户是否属于mappedValue任意一个角色
     *
     * @param subject 1
     * @param mappedValue 2
     * @return boolean
     */
    private boolean checkRoles(Subject subject, Object mappedValue){
        String[] rolesArray = (String[]) mappedValue;
        return rolesArray == null || rolesArray.length == 0 || Stream.of(rolesArray).anyMatch(role -> subject.hasRole(role.trim()));
    }


    public void setRedisTemplate(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }


    public void setSysUserService(SysUserService sysUserService) {
        this.sysUserService = sysUserService;
    }

    public void setJwtConfig(JwtConfig jwtConfig) {
		this.jwtConfig = jwtConfig;
	}
    
    /**
     * 判断并获取Respose
     * @param response
     * @return
     */
    private HttpServletResponse getAsHttpResponse(ServletResponse response) {
        if (!(response instanceof HttpServletResponse)) {
            throw new RuntimeException("Expecting an HTTP request");
        }

        return (HttpServletResponse) response;
    }
}
