package com.duiba.tuia.youtui.web.filter;

import cn.com.duiba.consumer.center.api.dto.ConsumerDto;
import cn.com.duiba.tuia.activity.center.api.constant.LoginType;
import cn.com.duiba.tuia.activity.center.api.constant.SlotLoginType;
import cn.com.duiba.tuia.ssp.center.api.dto.MediaAppDataDto;
import cn.com.duiba.tuia.ssp.center.api.dto.SlotDto;
import cn.com.duibaboot.ext.autoconfigure.accesslog.AccessLogFilter;
import com.alibaba.fastjson.JSONObject;
import com.duiba.tuia.youtui.web.bo.ConsumerBO;
import com.duiba.tuia.youtui.web.constant.Constants;
import com.duiba.tuia.youtui.web.constant.Page;
import com.duiba.tuia.youtui.web.embed.DuibaMonitor;
import com.duiba.tuia.youtui.web.exception.ActivityException;
import com.duiba.tuia.youtui.web.model.UserAccount;
import com.duiba.tuia.youtui.web.service.AppService;
import com.duiba.tuia.youtui.web.service.ConsumerService;
import com.duiba.tuia.youtui.web.service.ShieldRuleService;
import com.duiba.tuia.youtui.web.service.SlotService;
import com.duiba.tuia.youtui.web.tool.*;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

/**
 * ClassName: LoginFilter <br/>
 * date: 2016年12月5日 上午11:03:29 <br/>.
 *
 * @since JDK 1.6
 */
@Component
public class LoginInterceptor implements HandlerInterceptor {

    /**
     * The log.
     */
    private static Logger log = LoggerFactory.getLogger(LoginInterceptor.class);

    /**
     * 规则匹配
     */
    @Autowired
    private ShieldRuleService shieldRuleService;

    @Autowired
    private ConsumerService consumerService;

    @Autowired
    private AppService appService;

    @Autowired
    private ConsumerBO consumerBO;

    @Autowired
    private SlotService slotService;

    /**
     * Do filter.
     *
     * @param request  the req
     * @param response the resp
     * @throws IOException      the IO exception
     * @throws ServletException the servlet exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {


        //设置csp，只允许访问某些固定域名的css、js资源等，增加安全性,防止XSS攻击
        //TODO 待确认后改为Content-Security-Policy
        //response.setHeader("Content-Security-Policy-Report-Only","default-src *;img-src data: *;connect-src 'self';form-action 'self';frame-src 'self';child-src 'self';report-uri /csp/report;");

        RequestTool.setRequestInThreadLocal(request);
        RequestLocal.clear();
        RequestLocal.get().setResponse(response);
        RequestLocal.get().setRequest(request);

        String path = request.getServletPath();

        try {
            if (!Environment.isOnline() && request.isSecure()) {// 非线上环境，https访问时，不允许加载http的资源，方便测试早点发现问题.
                response.setHeader("Content-Security-Policy", "block-all-mixed-content");
            }
            //5.设置统一访问日志host
            setAccessLogHost();

            if (!Environment.isOnline()) {
                CookieUtil.setCrossCookieFor24Hour("userId", null == RequestLocal.get().getCid() ? "1" : RequestLocal.get().getCid().toString());
            }

            // 保存流量入口dpm
            // 记录活动页来源，活动页面链接携带来源字段时，保存cookie中,供后续接口继续使用
            // 当活动页重新加载时 没有携带来源字段，则替换来源字段为""
            if ("/activity/index".equals(path)) {
                setActivityIndexCookie(request);
            }
            if (!path.startsWith("/swagger") && !"/common/error".equals(path) && !nonLogin(request)) {
                // 处理免登陆逻辑
                response.sendRedirect("/common/error");
                return false;
            }

            // 黑名单检测
            if (!shieldRuleService.checkRequest(request)) {
                AccessLogFilter.putExPair("cheat_consumer", "true");
                return false;
            }
            // 统一访问日志记录post请求参数
            if ("post".equalsIgnoreCase(request.getMethod())) {
                setAccessLogPostParams(request.getParameterMap());
            }
            if (!Environment.isOnline()) {
                CookieUtil.setCrossCookieFor24Hour("userId", null == RequestLocal.get().getCid() ? "2" : RequestLocal.get().getCid().toString());
            }
            // 监控返回事件
            positionHandle(request);
            // 访问日志记录Ret
            accessLogRet(request);

            return true;
        } catch (Exception e) {
            log.warn("filter error", e);
        }

        return true;
    }

    private void accessLogRet(HttpServletRequest request) {
        String ret = RequestTool.getCookie(request, Constants.COOKIEKEY.RET);
        if (StringUtils.isNotBlank(ret) && !ret.equals("0.0")) {
            AccessLogFilter.putExPair(Constants.COOKIEKEY.RET, ret);
        }
    }

    /*
     思路说明：
        前提：
            1.url中的dsm后两位表示的是前页面位置，该url与dsm 在前页面时，通过后端接口返回
            2.cookie 实时刷新当前页面位置，刷新前获取到的必然是前页面位置
        判断返回事件
            cookie 获取到的前页面与dsm获取的前页面 不匹配时，则当前访问是属于返回事件产生的访问。cookie获取到的前页面位置是返回事件起点页

        注意：返回事件分为两种情况:1.返回会刷新,2.返回不会刷新
            情况1，可以正确记录返回起点页。
            情况2，就复杂了
            （1）A1-点击浮标>B1-点击区块>C1-无刷新返回>B1-点击区块>C2
            点击C2区块 状态分析：oldLocation=C1,dsm.ret=B1,Ret 会记录C1
            （2）A1-点击浮标>B1-点击区块>C1-无刷新返回>B1-点击区块>C1
            等同于在C1上刷新了一下
            （3）A1-点击浮标>B1-点击区块>C1-无刷新返回>B1-无刷新返回>A1-点击浮标>B2
            点击B2浮标 状态分析：oldLocation=C1,dsm.ret=A1,Ret 记录C1，但不会记录B1，A1的链路
     */
    private void positionHandle(HttpServletRequest request) {

        String path = request.getServletPath();
        String location = Page.location(path, request.getParameter(Constants.VALUENAME.ID));
        if (StringUtils.isBlank(location)) {//非页面访问的请求 pass
            return;
        }

        // location 刷新前，cookie获取到的location 是前页面的
        String oldLocation = RequestTool.getCookie(request, Constants.COOKIEKEY.LOCATION);
        String dsm = request.getParameter(Constants.KEY.DSM);
        //
        String ret = DuibaMonitor.getCD(dsm);

        if (isMaiden(request)) {// 第一次从广告位进来的请求
            cleanRet();
        } else if (location.equals(oldLocation)) {//页面刷新
            //doNothing
        } else if (StringUtils.isNotBlank(oldLocation) && StringUtils.isNotBlank(ret) && !oldLocation.equals(ret)) {//非第一次访问且识别为返回事件
            markRet(oldLocation);
        }

        markLocation(location);
    }


    /**
     * 判断是否第一次请求
     *
     * @param request
     * @return
     */
    private boolean isMaiden(HttpServletRequest request) {
        return "SOW".equals(request.getParameter(Constants.VALUENAME.TENTER))
                && !StringUtils.equals(request.getParameter(Constants.VALUENAME.TCK_RID), RequestTool.getCookie(request, Constants.COOKIEKEY.RID));
    }

    /**
     * cookie 标记当前位置
     *
     * @param location
     */
    private void markLocation(String location) {
        CookieUtil.setCrossCookieFor24Hour(Constants.COOKIEKEY.LOCATION, location);
    }

    /**
     * cookie 标记返回起点位置
     * 访问日志记录Ret
     *
     * @param ret
     */
    private void markRet(String ret) {
        CookieUtil.setCrossCookieFor24Hour(Constants.COOKIEKEY.RET, ret);
    }

    /**
     * 清除Ret
     */
    private void cleanRet() {
        CookieUtil.setCrossCookieForTimes(Constants.COOKIEKEY.RET, "", 0);
    }

    private void setActivityIndexCookie(HttpServletRequest request) {
        String sourceId = request.getParameter("sourceId");
        String sourceType = request.getParameter("sourceType");
        String dpm = request.getParameter("dpm");
        String dcm = request.getParameter("dcm");
        String dsm = request.getParameter("dsm");

        String id = request.getParameter(Constants.ID);
        String appKey = request.getParameter(Constants.APP_KEY);
        CookieUtil.setCrossCookieFor24Hour("_sourceId",
                StringUtils.isNotBlank(sourceId) ? sourceId : StringUtils.EMPTY);
        CookieUtil.setCrossCookieFor24Hour("_sourceType",
                StringUtils.isNotBlank(sourceType) ? sourceType : StringUtils.EMPTY);

        CookieUtil.putDmCookie(Constants.COOKIEKEY.ACT, id, dsm, dcm, dpm);

        CookieUtil.setCrossCookieFor24Hour(Constants.APP_KEY, appKey == null ? "" : appKey);
    }

    /**
     * 免登陆逻辑处理.
     *
     * @param request the request
     * @throws ActivityException the activity exception
     */
    private boolean nonLogin(HttpServletRequest request) {
        Long cId = RequestLocal.get().getCid();
        UserAccount account = RequestLocal.get().getUserAccount();
        // appKey
        String appKey = request.getParameter(Constants.APP_KEY);
        // 登录类型(loginType:normal\preview)
        String loginType = request.getParameter(Constants.LOGIN);

        // 用于统一日志处理
        String slotId = request.getParameter(Constants.SLOTID);

        // 用户设备号
        String deviceId = request.getParameter(Constants.DEVICE_ID);

        // 用户没有携带设备号
        deviceId = setDevice(account, deviceId);

        // 1.根据登录类型获取appID
        Long appId = getAppIdByLoginType(loginType, appKey, account, deviceId);
        try {
            if (LoginType.PREVIEW.equalsCode(loginType)) {
                consumerService.getOrMakeUser(appId, ConsumerDto.PREVIEWUSERID);
                return true;
            }
            if (appId != null && StringUtils.isNotBlank(slotId)) {
                //wx和第三方用户登录情况下 需要自动登录

                Optional<SlotDto> slot = slotService.getSlot(Long.valueOf(slotId));
                Integer slotLoginType = slot.get().getPlugBuoyConfigDto().getLoginType();
                if (SlotLoginType.WEIXIN.getType().equals(slotLoginType)) {
                    consumerBO.wxAutoLogin(request, slot.get().getPlugBuoyConfigDto(), appId, deviceId);
                } else {
                    consumerService.getOrMakeUser(appId, deviceId);
                }

                // 3.判断用户信息与缓存是否一致，不一致，刷新缓存,如果一致 已缓存为准。 避免用户修改活动连接中的soltID ，导致广告位ID不准
                setCollData(slotId, appId);

                // 4.将用户信息变量写入统一日志
                setLogPropertyValue();
            }
        } catch (Exception e) {
            log.error("登录失败，appId:{}，slotId:{}，result={}", appId, slotId, e);
            return false;
        }
        return true;
    }

    private String setDevice(UserAccount account, String deviceId) {
        if (StringUtils.isBlank(deviceId) || "null".equals(deviceId)) {
            if (null == account || null == account.getPartnerUserId() || "null".equals(account.getPartnerUserId())) {
                deviceId = UUID.randomUUID().toString();
            } else {
                deviceId = account.getPartnerUserId();
            }
        }


        if (StringUtils.isNotBlank(deviceId)) {
            CookieUtil.setCrossCookieFor24Hour("_coll_device", deviceId);
            AccessLogFilter.putLogCookie("_coll_device", deviceId);
        }
        return deviceId;
    }


    private void setCollData(String slotId, Long appId) {
        // 将slotID放入缓存
        if (StringUtils.isNotBlank(slotId)) {
            CookieUtil.setCrossCookieFor24Hour("_coll_" + appId + "_slot", slotId);
            CookieUtil.setCrossCookieFor24Hour("_coll_slot", slotId);
            AccessLogFilter.putLogCookie("_coll_slot", slotId);
        }

        //如果根据appid获取的slotID不为null，那么强制刷新cookie中的_coll_slot
        if (StringUtils.isNotBlank(RequestLocal.get().getSlotIdByAppId(appId))) {
            CookieUtil.setCrossCookieFor24Hour("_coll_slot", RequestLocal.get().getSlotIdByAppId(appId));
            CookieUtil.setCrossCookieFor24Hour("_coll_" + appId + "_slot", slotId);
            AccessLogFilter.putLogCookie("_coll_slot", RequestLocal.get().getSlotIdByAppId(appId));
        }
    }


    /**
     * getAppIdByLoginType:根据登录类型获取appID. <br/>
     * Normal 普通用户通过appKey获取appID.<br/>
     * Preview 预览用户设置appID为默认值1.<br/>
     * 普通用户ajax请求可能未携带appkey
     *
     * @param loginType the login type
     * @param appKey    the app key
     * @param account   the account
     * @param deviceId  the device id
     * @return the app id by login type
     * @author cdm
     * @since JDK 1.7
     */
    private Long getAppIdByLoginType(String loginType, String appKey, UserAccount account, String deviceId) {

        // 1、appID 默认值 1L
        Long appId = null;
        try {
            // 2、普通用户根据APPKey获取appID
            if (LoginType.PREVIEW.equalsCode(loginType)) {
                // 预览用户默认1
                appId = 1L;
            } else {
                if (null != appKey) {
                    // 3.查询数据库中的appId
                    MediaAppDataDto app = appService.findAppByAppKey(appKey);
                    if (null != app) {
                        appId = app.getAppId();
                    }
                } else {
                    // 4、ajax请求时appkey可能没带，所以为null，去cookie里面拿
                    appId = account != null ? account.getAppId() : appId;
                }
            }
        } catch (Exception e) {
            log.warn("LoginFilter=>getAppIdByLoginType=>BusinessActivityDeliveryTool.get().findAppByAppKey非法的appkey。this error IP = " + RequestLocal.get().getIp() + " and error slotId = " + RequestLocal.get().getSlotIdByAppId(RequestLocal.get().getAppId()));
        }

        return appId;
    }

    /**
     * setLogPropertyValue:设置统一日志属性变量值，便于打统一日志. <br/>
     *
     * @author cdm
     * @since JDK 1.7
     */
    private void setLogPropertyValue() {
        // 配合日志,这是一个强依赖,暂时没有更好的方案,先这么实现
        UserAccount userAccount = RequestLocal.get().getUserAccount();
        AccessLogFilter.ConsumerId.set(userAccount.getUserId());
        AccessLogFilter.AppId.set(userAccount.getAppId());
    }

    /**
     * 设置统一访问日志host.
     */
    private void setAccessLogHost() {
        // 统一访问日志中 保证host均为activity.tuia.cn 便于统计
        AccessLogFilter.putOverWritePair(AccessLogFilter.OW_HOST, DomainConstantUtil.getYoutuiDomain());
    }

    /**
     * 统一访问日志记录post请求参数
     *
     * @param params
     */
    private void setAccessLogPostParams(Map<String, String[]> params) {
        JSONObject jsonObject = new JSONObject();
        if (params != null) {
            for (Map.Entry<String, String[]> entry : params.entrySet()) {
                if (entry.getValue() != null) {
                    Integer paramLength = 0;
                    for (String value : entry.getValue()) {
                        paramLength += value.length();
                    }
                    if (paramLength < 80) {
                        jsonObject.put(entry.getKey(), entry.getValue());
                    }
                }
            }
        }
        String queryString = jsonObject.toString();
        if (queryString.length() > 300) {
            queryString = queryString.substring(0, 300);
        }
        AccessLogFilter.putExPair("queryString", queryString);
    }


    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

        return;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        return;
    }
}
