/*
 * Copyright (C), 2002-2015, 江苏三六五网络股份有限公司
 * FileName: InterfaceController.java
 * Author:   duhui
 * Date:     2015年1月22日 上午11:39:07
 * Description:
 * History:
 * <author>      <time>      <version>    <desc>
 * 修改人姓名                          修改时间                        版本号                                 描述
 */
package com.house365.web.controller;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.house365.beans.po.IfRegPo;
import com.house365.beans.vo.rest.UserCreditsVO;
import com.house365.commons.system.RemoteIpUtils;
import com.house365.rest.context.IService;
import com.house365.rest.context.Parameter;
import com.house365.rest.context.ServiceConstant;
import com.house365.rest.context.ServiceContext;
import com.house365.rest.exception.IllegalServiceParameterException;
import com.house365.rest.exception.ServiceConfigException;
import com.house365.rest.parameter.House365RestResponse;
import com.house365.web.cached.RedisUtilsInterface;
import com.house365.web.interceptor.UrlParamAuthInterceptor;
import com.house365.web.system.controller.BaseController;
import com.house365.web.util.Constant;
import com.house365.web.util.House365StringUtils;
import com.house365.web.util.MemoryPropertyPlaceholderConfigurer;
import com.house365.ws.interfaces.server.IIfRegInterface;
import com.house365.ws.system.ReturnAppResult;
import com.house365.ws.util.BeanUtil;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.jasig.cas.client.session.SessionMappingStorage;
import org.jasig.cas.client.util.AssertionHolder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Transaction;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.house365.web.util.Constant.SERVICE_COUNT_CODE;

/**
 * 对外统一接口
 *
 * @author duhui
 * @version [v1.0.0, 2015年1月22日]
 * @see [相关类/方法]（可选）
 * @since [产品/模块版本] （可选）
 */
@Controller
@RequestMapping(value = {"/rest", "/secure/rest"})
public class InterfaceController extends BaseController {

    /**
     * 日志记录器
     */
    private static final Logger LOGGER = LoggerFactory.getLogger("InterfaceLog");
    @Autowired
    RedisUtilsInterface redisUtils;
    /**
     * 注入接口服务
     */
    @Autowired
    private IIfRegInterface ifRegInterface;
    private SessionMappingStorage sessionMappingStorage;

    /**
     * 统一入口分发
     *
     * @param request 公共参数
     * @return 处理结果json字符串
     * @author duhui
     * @version [v1.0.0, 2015年1月22日]
     */
    @SuppressWarnings("unchecked")
    @RequestMapping(value = "interface", method = {RequestMethod.GET, RequestMethod.POST}, produces = {"application/json;charset=UTF-8"})
    @ResponseBody
    public String interfaceOut(HttpServletRequest request, HttpServletResponse servletResponse) throws Exception {
        House365RestResponse<Object> response = new House365RestResponse<>();
        Object result = null;
        Map<String, Object> map = new HashMap<String, Object>(request.getParameterMap());

        // 统一入口校验 业务层只需校验自己所需参数是否存在
        // 标识来源
        String serviceCode;
        // 服务方法名
        String serviceName;
        try {
            serviceCode = String.valueOf(map.get(
                ServiceConstant.SERVICE_CODE).getClass().isArray() ? ((String[]) map.get(
                ServiceConstant.SERVICE_CODE))[0] : map.get(ServiceConstant.SERVICE_CODE));
            serviceName = String.valueOf(map.get(
                ServiceConstant.SERVICE_NAME).getClass().isArray() ? ((String[]) map.get(
                ServiceConstant.SERVICE_NAME))[0] : map.get(ServiceConstant.SERVICE_NAME));
            if (Strings.isNullOrEmpty(serviceCode) || Strings.isNullOrEmpty(serviceName)) {
                LOGGER.error("配置执行服务异常：找不到对应服务编码 服务名称!");
                throw new ServiceConfigException("配置执行服务异常：找不到对应服务编码 服务名称!");
            }

            // 获取对端IP
            final String userIp = RemoteIpUtils.getIpAddr(request);
            String host = MemoryPropertyPlaceholderConfigurer.getContextProperty("redis.host");
            final String key = serviceName + userIp;
            String env = MemoryPropertyPlaceholderConfigurer.getContextProperty("system.env");

            final String fuckIps = "fuck_ips";
            if (!"test".equalsIgnoreCase(env) && !userIp.contains("172.17.") && !userIp.contains("153.3.56.90") && !userIp.contains(
                "121.40.44.159") && !userIp.contains("192.168.") && !userIp.contains("127.0.0.1") && !userIp.contains(
                "218.94.115.131")) {
                JedisPool pool = new JedisPool(new JedisPoolConfig(), host);
                try (Jedis jedis = pool.getResource()) {
                    String current = jedis.get(key);

                    if (jedis.exists(fuckIps)) {
                        Set<String> ips = jedis.smembers(fuckIps);
                        if (ips != null && !ips.isEmpty() && ips.contains(userIp)) {
                            LOGGER.warn("请求过于频繁，屏蔽访问! [{}]", key);
                            response.setResult(ReturnAppResult.APP_FAIL.getResultCode());
                            response.setMsg("请求过于频繁!");
                            return JSONObject.fromObject(response).toString();
                        }
                    }

                    if (!Strings.isNullOrEmpty(current) && com.house365.web.util.StringUtils.isNumeric(
                        current) && (Integer.parseInt(current)) >= 5) {
                        LOGGER.warn("请求过于频繁，请稍后再试! [{}]", key);
                        response.setResult(ReturnAppResult.APP_FAIL.getResultCode());
                        response.setMsg("请求过于频繁，请稍后再试!");
                        jedis.sadd(fuckIps, userIp);
                        jedis.expire(fuckIps, 60 * 60);
                        return JSONObject.fromObject(response).toString();
                    } else {
                        Transaction t = jedis.multi();
                        t.incr(key);
                        t.expire(key, 1);
                        t.exec();
                    }
                } finally {
                    pool.close();
                }
            }

            checkInterfaceParameter(map);
        } catch (IllegalServiceParameterException e) {
            response.setResult(ReturnAppResult.APP_FAIL.getResultCode());
            response.setMsg(e.getMessage());
            return JSONObject.fromObject(response).toString();
        }
        // 接口服务校验 如果有异常可发送消息通知负责人
        IService service;
        try {
            service = checkAndGetServiceReg(map);
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
            response.setResult(ReturnAppResult.APP_FAIL.getResultCode());
            response.setMsg(e.getMessage());
            return JSONObject.fromObject(response).toString();
        }

        // 从 header 中取出 house365 自定义参数，如 uid、deviceid 等，";"分隔
        String house365Header = request.getHeader("X-House365-Client");
        if (StringUtils.isNotBlank(house365Header)) {
            String[] argsArr = house365Header.split(";");
            if (ArrayUtils.isNotEmpty(argsArr)) {
                for (String argPair : argsArr) {
                    if (StringUtils.isNotBlank(argPair) && argPair.startsWith("uid=") && !map.containsKey("userId")) {
                        String uid = argPair.substring(argPair.indexOf("=") + 1, argPair.length());
                        map.put("userId", uid == null ? StringUtils.EMPTY : uid);
                    }
                    if (StringUtils.isNotBlank(argPair) && argPair.startsWith("deviceid=")) {
                        String deviceid = argPair.substring(argPair.indexOf("=") + 1, argPair.length());
                        map.put("deviceId", deviceid == null ? StringUtils.EMPTY : deviceid);
                    }
                }
            }
        }

        //uid触屏不放在header中传递,get form url
        if (!map.containsKey("userId") || map.get("userId") == null) {
            if (map.containsKey("uid") && map.get("uid") != null) {
                String[] uid = (String[]) map.get("uid");
                if (uid != null && uid.length > 0) {
                    map.put("userId", uid[0]);
                }
            }
        }

        if (!map.containsKey("deviceId") || map.get("deviceId") == null) {
            if (map.containsKey("deviceid") && map.get("deviceid") != null) {
                String[] deviceid = (String[]) map.get("deviceid");
                if (deviceid != null && deviceid.length > 0) {
                    map.put("deviceId", deviceid[0]);
                }
            }
        }

        //都没有,检查cas中是否存在
        if ((!map.containsKey("userId") || map.get("userId") == null) && request.getRequestURI().contains(
            "secure") && AssertionHolder.getAssertion() != null) {
            String cas = (String) AssertionHolder.getAssertion().getPrincipal().getAttributes().get("passport_uid");
            if (!Strings.isNullOrEmpty(cas)) {
                map.put("userId", cas);
            }
        }

        // 封装请求参数
        Parameter parameter = new Parameter();
        //封装当前的host

        Map<String, Object> modifiableMap = new HashMap<>();
        modifiableMap.putAll(map);
        parameter.setArgs(modifiableMap);
        // 之前接口只可以执行execute一个方法,保持接口纯洁同时增加了编码复杂度
        // 修改后：可自定义接口调用方法，但仍需注意保持接口功能单一 By xn 20151229
        if (Constant.CM_SERVICE_CODE.equals(serviceCode)) {
            result = ServiceContext.getInstance().startService(service, parameter);
        } else if (Constant.HMC_SERVICE_CODE.equals(serviceCode)) {
            result = ServiceContext.getInstance().startServiceWithMethod(service, map, serviceName);
        }

        servletResponse.setHeader("Access-Control-Allow-Origin", "http://*.house365.com");

        if (null == result || Strings.isNullOrEmpty(String.valueOf(result))) {
            LOGGER.error("RESULT IS NULL !!! PARAMETER [{}]", map);
            response.setResult(ReturnAppResult.APP_FAIL.getResultCode());
            response.setMsg("返回结果为空");
            return JSONObject.fromObject(response).toString();
        } else {
            //特殊或失败的记录详细参数
            if ((!Strings.isNullOrEmpty(serviceName) && Lists.newArrayList(UrlParamAuthInterceptor.logApis).contains(
                serviceName)) || result.toString().contains(
                "\"result\":\"" + ReturnAppResult.APP_FAIL.getResultCode() + "\"")) {
                StringBuilder ps = new StringBuilder("");
                for (Map.Entry entry : map.entrySet()) {
                    String value = entry.getValue().getClass().isArray() ? ((String[]) entry.getValue())[0] : String.valueOf(
                        entry.getValue());
                    ps.append(entry.getKey()).append(":").append(value).append(",");
                }
                LOGGER.info("CALL RESULT [{}] , PARAMETER [{}]",
                            result.toString().length() > 200 ? result.toString().substring(0, 200) : result.toString(),
                            ps);
            }
        }

        return result.toString();
    }

    /**
     * 统一分发中心接口参数校验
     *
     * @param map 与小飞讨论后更改请求参数传递方式
     * @throws IllegalServiceParameterException 统一分发中心接口参数异常
     * @since [产品/模块版本](可选)
     */
    protected void checkInterfaceParameter(Map<String, Object> map) throws IllegalServiceParameterException {
        if (!map.containsKey("platform")) {
            map.put("platform", new String[] {"1"});
        }

        // 遍历校验
        for (String key : map.keySet()) {
            try {
                String value = StringUtils.EMPTY; // key 对应的 value 值
                String[] valueArray = ((String[]) map.get(key));
                if (ArrayUtils.isNotEmpty(valueArray)) {
                    value = valueArray[0];
                }
                if (House365StringUtils.isEmpty(value)) {
                    LOGGER.warn("业务入口参数异常：" + key + "参数为空!");
                    //                    throw new IllegalServiceParameterException("业务入口参数异常：" + key + "参数为空!");
                }
            } catch (Exception e) {
                throw new IllegalServiceParameterException("业务入口参数异常{" + e.getMessage() + "}：" + key + "参数为空!");
            }
        }
    }

    /**
     * 校验并取得公共容器配置的执行服务
     *
     * @param map 入参
     * @throws ServiceConfigException 服务参数配置异常
     * @since [产品/模块版本](可选)
     */
    protected IService checkAndGetServiceReg(Map<String, Object> map) throws ServiceConfigException {
        /**
         * 取得公共容器执行服务
         */
        IService iService;
        try {

            String serviceCode = String.valueOf(map.get(
                ServiceConstant.SERVICE_CODE).getClass().isArray() ? ((String[]) map.get(
                ServiceConstant.SERVICE_CODE))[0] : map.get(ServiceConstant.SERVICE_CODE));
            String serviceName = String.valueOf(map.get(
                ServiceConstant.SERVICE_NAME).getClass().isArray() ? ((String[]) map.get(
                ServiceConstant.SERVICE_NAME))[0] : map.get(ServiceConstant.SERVICE_NAME));

            final String serviceCacheKey = "cm_ifReg:" + serviceCode + ":" + serviceName;
            String serviceIdentify = "";
            try {
                serviceIdentify = redisUtils.getValByKey(serviceCacheKey);
            } catch (Exception e) {
                LOGGER.error("配置执行服务异常：配置的执行服务缓存获取失败!", e);
            }

            final String serviceVisitCountCacheKey = SERVICE_COUNT_CODE + serviceName;
            redisUtils.increaseByKey(serviceVisitCountCacheKey);
            IfRegPo ifRegPo = null;
            if (Strings.isNullOrEmpty(serviceIdentify)) {
                // 根据服务编码 服务名称 取得执行的服务
                List<IfRegPo> ifRegPoList = ifRegInterface.getIfRegServiceReg(serviceCode, serviceName);
                // 找不到执行的服务
                if (null == ifRegPoList || ifRegPoList.isEmpty()) {
                    LOGGER.error("配置执行服务异常：根据服务编码 服务名称找不到对应执行服务!");
                    throw new ServiceConfigException("配置执行服务异常：根据服务编码 服务名称找不到对应执行服务!");
                }
                // 找到多个执行的服务 默认取一个执行 须记录改正下
                if (ifRegPoList.size() > 1) {
                    LOGGER.warn("配置执行服务警告：根据服务编码 服务名称找到多个执行服务!");
                }
                // 取得执行的服务
                ifRegPo = ifRegPoList.get(0);
                serviceIdentify = ifRegPo.getServiceReg();

                try {
                    redisUtils.addKeyVal(serviceCacheKey, serviceIdentify);
                } catch (Exception e) {
                    LOGGER.error("配置执行服务异常：配置的执行服务缓存设置失败!", e);
                }
            }
            if (ifRegPo != null) {
                map.put("serviceReg", ifRegPo.getServiceReg());
            }
            iService = ServiceContext.getInstance().createServiceInstance(serviceIdentify);
        } catch (Exception e) {
            LOGGER.error("配置执行服务异常：配置的执行服务没有发布!", e);
            throw new ServiceConfigException("配置执行服务异常：配置的执行服务没有发布!");
        }
        return iService;
    }

    @RequestMapping(value = "getUserCreditsAPI", method = {RequestMethod.POST})
    @ApiOperation(value = "获取用户积分信息", httpMethod = "POST", notes = "{\"serviceCode\":\"Cm\",\"serviceName\":\"GetUserCreditsInfo\",\"userId\": \"7\",\"userCityKey\": \"nj\",\"useridType\": \"1\"}", response = Object.class)
    @ResponseBody
    public Object getUserCreditsInfo(
        @ApiParam(required = true) @RequestBody @Valid UserCreditsVO userCreditsVO, BindingResult error
    ) {
        return callService(userCreditsVO, error);
    }

    @RequestMapping(value = "getUserBillListAPI", method = {RequestMethod.POST})
    @ApiOperation(value = "获取用户积分收支明细", httpMethod = "POST", notes = "{\"serviceCode\":\"Cm\",\"serviceName\":\"GetUserBillList\",\"userId\": \"1601981\",\"userCityKey\": \"nj\",\"useridType\": \"1\",\"pageSize\":\"10\",\"currentPage\":\"1\"}", response = Object.class)
    @ResponseBody
    public Object getUserBillList(
        @ApiParam(required = true) @RequestBody @Valid UserCreditsVO userCreditsVO, BindingResult error
    ) {
        return callService(userCreditsVO, error);
    }

    @RequestMapping(value = "casLogout")
    @ResponseBody
    public void casLogout(HttpServletRequest request) {
        LOGGER.info("this is sessionDestroyed[{},{},{}]", request.getRequestURI(), request.getRequestURL(),
                    request.getParameter("logoutRequest"));
    }

    private Object callService(
        @ApiParam(required = true) @RequestBody @Valid UserCreditsVO userCreditsVO, BindingResult error
    ) {
        Object result = new House365RestResponse<>();
        if (error.hasErrors()) {
            if (LOGGER.isWarnEnabled()) {
                // 构造消息体
                LOGGER.warn(buildErrorMessage(error));
            }
        } else {
            try {
                Map<String, Object> map = BeanUtil.transBean2Map(userCreditsVO);
                IService service;
                service = checkAndGetServiceReg(map);
                // 封装请求参数
                Parameter parameter = new Parameter();
                //封装当前的host
                parameter.setArgs(map);
                // 执行结果
                result = ServiceContext.getInstance().startService(service, parameter);
            } catch (Exception e) {
                e.printStackTrace();
                return result;
            }
        }
        return result;
    }

    /**
     * 错误信息构造
     *
     * @param error 验证错误
     * @return 验证错误信息
     */
    private String buildErrorMessage(BindingResult error) {
        JSONArray errorJson = JSONArray.fromObject(error.getAllErrors());
        return "参数异常:" + errorJson.toString();
    }


}
