/*
 * Copyright [2020] [MaxKey of copyright http://www.maxkey.top]
 *
 * 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 org.maxkey.authn.support.socialsignon;

import javax.servlet.http.HttpServletRequest;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.dingtalk.api.DefaultDingTalkClient;
import com.dingtalk.api.DingTalkClient;
import com.dingtalk.api.request.OapiGettokenRequest;
import com.dingtalk.api.request.OapiUserGetbyunionidRequest;
import com.dingtalk.api.request.OapiV2UserGetRequest;
import com.dingtalk.api.response.OapiGettokenResponse;
import com.dingtalk.api.response.OapiUserGetbyunionidResponse;
import com.dingtalk.api.response.OapiV2UserGetResponse;
import com.dingtalk.api.response.OapiV2UserListResponse;
import com.taobao.api.ApiException;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.maxkey.authn.AbstractAuthenticationProvider;
import org.maxkey.authn.LoginCredential;
import org.maxkey.authn.support.jwt.JwtLoginService;
import org.maxkey.authn.support.socialsignon.service.SocialsAssociate;
import org.maxkey.constants.ConstantsLoginType;
import org.maxkey.constants.ConstantsPasswordSetType;
import org.maxkey.entity.HistoryLogin;
import org.maxkey.entity.HistoryLogs;
import org.maxkey.entity.SocialsProvider;
import org.maxkey.entity.UserInfo;
import org.maxkey.persistence.db.LoginHistoryService;
import org.maxkey.persistence.redis.RedisConnection;
import org.maxkey.persistence.redis.RedisConnectionFactory;
import org.maxkey.persistence.service.HistorySystemLogsService;
import org.maxkey.persistence.service.UserInfoService;
import org.maxkey.util.StringUtils;
import org.maxkey.web.WebContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.web.WebAttributes;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;

import me.zhyd.oauth.request.AuthRequest;

import java.util.Random;

/**
 * @author Crystal.Sea
 *
 */
@Controller
@RequestMapping(value = "/logon/oauth20")
public class SocialSignOnEndpoint extends AbstractSocialSignOnEndpoint
{
    @Value("${maxkey.callback.url}")
    private String callbackUrl;

    @Autowired
    @Qualifier("authenticationProvider")
    AbstractAuthenticationProvider authenticationProvider;

    @Autowired
    @Qualifier("jwtLoginService")
    JwtLoginService jwtLoginService;

    @Autowired
    @Qualifier("userInfoService")
    private UserInfoService userInfoService;


    @Autowired
    RedisConnectionFactory redisConnectionFactory;



    final static Logger _logger = LoggerFactory.getLogger(SocialSignOnEndpoint.class);

    public ModelAndView socialSignOnAuthorize(HttpServletRequest request, String provider)
    {
        _logger.trace("SocialSignOn provider : " + provider);
        String authorizationUrl = buildAuthRequest(provider).authorize(request.getSession().getId());
        _logger.trace("authorize SocialSignOn : " + authorizationUrl);
        return WebContext.redirect(authorizationUrl);
    }

    @RequestMapping(value = {"/authorize/{provider}"}, method = RequestMethod.GET)
    public ModelAndView authorize(HttpServletRequest request,
                                  @PathVariable String provider)
    {
        WebContext.setAttribute(SOCIALSIGNON_TYPE_SESSION, SOCIALSIGNON_TYPE.SOCIALSIGNON_TYPE_LOGON);
        return socialSignOnAuthorize(request, provider);
    }

    @RequestMapping(value = {"/bind/{provider}"}, method = RequestMethod.GET)
    public ModelAndView bind(HttpServletRequest request,
                             @PathVariable String provider)
    {
        WebContext.setAttribute(SOCIALSIGNON_SESSION_REDIRECT_URI, request.getParameter(SOCIALSIGNON_REDIRECT_URI));
        WebContext.setAttribute(SOCIALSIGNON_TYPE_SESSION, SOCIALSIGNON_TYPE.SOCIALSIGNON_TYPE_BIND);
        return socialSignOnAuthorize(request, provider);
    }

    @RequestMapping(value = {"/unbind/{provider}"}, method = RequestMethod.GET)
    public ModelAndView unbind(HttpServletRequest request,
                               @PathVariable String provider)
    {
        WebContext.setAttribute(SOCIALSIGNON_SESSION_REDIRECT_URI, request.getParameter(SOCIALSIGNON_REDIRECT_URI));
        SocialsAssociate socialSignOnUser = new SocialsAssociate();
        socialSignOnUser.setProvider(provider);
        socialSignOnUser.setUserId(WebContext.getUserInfo().getId());
        socialSignOnUser.setUsername(WebContext.getUserInfo().getUsername());
        _logger.debug("Social Sign On unbind {} from user {}",
                provider,
                WebContext.getUserInfo().getUsername()
        );

        socialsAssociateService.delete(socialSignOnUser);

        Object redirect_uri = WebContext.getAttribute(SOCIALSIGNON_SESSION_REDIRECT_URI);
        if (redirect_uri != null)
        {
            return WebContext.redirect(redirect_uri.toString());
        } else
        {
            return WebContext.forward("/socialsignon/list");
        }

    }

    @RequestMapping(value = {"/authorize/{provider}/{appid}"}, method = RequestMethod.GET)
    public ModelAndView authorize2AppId(HttpServletRequest request,
                                        @PathVariable("provider") String provider,
                                        @PathVariable("appid") String appid)
    {
        WebContext.setAttribute(SOCIALSIGNON_SESSION_REDIRECT_URI, "/authorize/" + appid);
        return authorize(request, provider);
    }

    @RequestMapping(value = {"/scanqrcode/{provider}"}, method = RequestMethod.GET)
    @ResponseBody
    public SocialsProvider scanQRCode(
            HttpServletRequest request,
            @PathVariable("provider") String provider)
    {
        AuthRequest authRequest = buildAuthRequest(provider);

        if (authRequest == null)
        {
            _logger.error("build authRequest fail .");
        }
        String state = request.getSession().getId();
        authRequest.authorize(state);

        SocialsProvider socialSignOnProvider = socialSignOnProviderService.get(provider);
        SocialsProvider scanQRCodeProvider = new SocialsProvider();

        scanQRCodeProvider.setId(socialSignOnProvider.getId());
        scanQRCodeProvider.setProvider(socialSignOnProvider.getProvider());
        scanQRCodeProvider.setProviderName(socialSignOnProvider.getProviderName());
        scanQRCodeProvider.setState(state);
        scanQRCodeProvider.setClientId(socialSignOnProvider.getClientId());
        scanQRCodeProvider.setRedirectUri(applicationConfig.getServerPrefix() +
                "/logon/oauth20/callback/" + provider);
        scanQRCodeProvider.setAgentId(socialSignOnProvider.getAgentId());

        return scanQRCodeProvider;
    }

    /**
     * 获取accessToken
     *
     * @return
     * @throws ApiException
     */
    public String getAccessToken() throws ApiException
    {
        SocialsProvider socialSignOnProvider = socialSignOnProviderService.get(provider);
        DingTalkClient client = new DefaultDingTalkClient("https://oapi.dingtalk.com/gettoken");
        OapiGettokenRequest request = new OapiGettokenRequest();
        request.setAppkey(socialSignOnProvider.getClientId());
        request.setAppsecret(socialSignOnProvider.getClientSecret());
        request.setHttpMethod("GET");
        OapiGettokenResponse response = client.execute(request);
        return response.getAccessToken();
    }


//	@RequestMapping(value={"/callback/{provider}"}, method = RequestMethod.GET)
//	public ModelAndView callback(@PathVariable String provider) {
//		 //auth call back may exception
//	    try {
//	    	SocialsAssociate socialsAssociate = null;
//    		this.provider=provider;
//    		this.authCallback();
//    		_logger.debug(this.accountId);
//    		socialsAssociate =new SocialsAssociate();
//    		socialsAssociate.setProvider(provider);
//    		socialsAssociate.setSocialUserId(this.accountId);
//
//    		//for login
//    		String socialSignOnType=
//    		        (WebContext.getAttribute(SOCIALSIGNON_TYPE_SESSION)!=null) ?
//    		                  (WebContext.getAttribute(SOCIALSIGNON_TYPE_SESSION).toString()) : "";
//
//
//    		if(socialSignOnType.equals(SOCIALSIGNON_TYPE.SOCIALSIGNON_TYPE_LOGON)
//    		        ||socialSignOnType.equals("")){
//    			socialSignOn(socialsAssociate);
//    			return WebContext.redirect("/index");
//    		}else{
//    			socialBind(socialsAssociate);
//    		}
//    		Object redirect_uri = WebContext.getAttribute(SOCIALSIGNON_SESSION_REDIRECT_URI);
//    		if(redirect_uri != null){
//    			return WebContext.redirect(redirect_uri.toString());
//    		}else{
//    			return WebContext.forward("/socialsignon/list");
//    		}
//
//	    }catch(Exception e) {
//	        _logger.error("callback Exception  ",e);
//	    }
//
//	    return WebContext.redirect("/login");
//	}

    /**
     *  重写钉钉回到方法
     * @return
     */
    @RequestMapping(value = {"/callback/{provider}"}, method = RequestMethod.GET)
    public ModelAndView callbackDingTalk(@PathVariable String provider,HttpServletRequest request)
    {
        // 对 callbackUrl 进行处理
        String curl = callbackUrl;
        if (StringUtils.isNotEmpty(curl))
        {
            if (curl.contains("?") || curl.contains("&"))
            {
                curl += "&ticket=";
            } else
            {
                curl += "?ticket=";
            }
        }
        // 获取access_token，注意正式代码要有异常流处理
        //
        String access_token = "";

        try
        {
            access_token = getAccessToken();
            _logger.debug("access_token :{}" + access_token);
        } catch (Exception e)
        {

        }
        //auth call back may exception
        // TODO  钉钉 回调函数
        try
        {
            SocialsAssociate socialsAssociate = null;
            this.provider = provider;
            this.authCallback();
            _logger.debug(this.accountId);
            socialsAssociate = new SocialsAssociate();
            socialsAssociate.setProvider(provider);
            socialsAssociate.setSocialUserId(this.accountId);

            //设定是登录
            WebContext.setAttribute(SOCIALSIGNON_TYPE_SESSION,"socialsignon_type_logon");
            //for login
            String socialSignOnType =
                    (WebContext.getAttribute(SOCIALSIGNON_TYPE_SESSION) != null) ?
                            (WebContext.getAttribute(SOCIALSIGNON_TYPE_SESSION).toString()) : "";


            if (socialSignOnType.equals(SOCIALSIGNON_TYPE.SOCIALSIGNON_TYPE_LOGON)
                    || socialSignOnType.equals(""))
            {
                this.socialSignOnProvider = socialSignOnProviderService.get(this.provider);
                socialSignOn(socialsAssociate);
                // 先判断用户是否存在
                // 1. 不存在 先存入用户
                // 根据unionid获取userid
                DingTalkClient clientDingTalkClient = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/user/getbyunionid");
                OapiUserGetbyunionidRequest reqGetbyunionidRequest = new OapiUserGetbyunionidRequest();
                reqGetbyunionidRequest.setUnionid(accountId);
                OapiUserGetbyunionidResponse oapiUserGetbyunionidResponse = clientDingTalkClient.execute(reqGetbyunionidRequest, getAccessToken());
                _logger.debug("accessToken >>>" + getAccessToken());
                // 根据userId获取用户信息
                String userid = oapiUserGetbyunionidResponse.getResult().getUserid();
                _logger.debug("userid >>>" + userid);
                DingTalkClient clientDingTalkClient2 = new DefaultDingTalkClient("https://oapi.dingtalk.com/topapi/v2/user/get");
                OapiV2UserGetRequest reqGetRequest = new OapiV2UserGetRequest();
                reqGetRequest.setUserid(userid);
                reqGetRequest.setLanguage("zh_CN");
                OapiV2UserGetResponse rspGetResponse = clientDingTalkClient2.execute(reqGetRequest, getAccessToken());
                OapiV2UserGetResponse.UserGetResponse userGetResponse = rspGetResponse.getResult();

                UserInfo userInfo = userInfoService.getMapper().queryUserInfoByAssociateId(userid);
                if (null == userInfo)
                {
                    userInfo = buildUserInfo(userGetResponse);
                    userInfoService.merge(userInfo);

                    //绑定用户
                    socialsAssociate.setUserId(userInfo.getId());
                    socialsAssociate.setUsername(userInfo.getUsername());
                    socialsAssociateService.delete(socialsAssociate);
                    socialsAssociateService.insert(socialsAssociate);
                }

                // 2. 存在 直接登录拼接ticket
                String token = jwtLoginService.buildLoginJwt();
                RedisConnection redisConnection = redisConnectionFactory.getConnection();
                // 默认 7天
                // 先获取之前有没有token
                String oldToken = redisConnection.get(userInfo.getId());
                if (com.alibaba.cloud.commons.lang.StringUtils.isNotEmpty(oldToken))
                {
                    redisConnection.delete(userInfo.getId());
                    redisConnection.delete(oldToken);
                }
                redisConnection.setex(userInfo.getId(), 60 * 60 * 24 * 7, token);
                redisConnection.setex(token, 60 * 60 * 24 * 7, JSON.toJSONString(userInfo));
                String ticket = createRandomCharData(36);

                JSONObject result = new JSONObject();
                result.put("token", token);
                result.put("userInfo", userInfo);
                redisConnection.setex(ticket, 60 * 60 * 24 * 1, result.toJSONString());
                redisConnection.close();
                _logger.debug("token >>>" + token);

                // TODO  整合前端的地址
                return WebContext.redirect(curl + ticket + "&sessionId="+ WebContext.getSession().getId());
            } else
            {
                socialBind(socialsAssociate);
            }

            Object redirect_uri = WebContext.getAttribute(SOCIALSIGNON_SESSION_REDIRECT_URI);
            if (redirect_uri != null)
            {
                return WebContext.redirect(redirect_uri.toString());
            } else
            {
                return WebContext.forward("/socialsignon/list");
            }

        } catch (Exception e)
        {
            _logger.error("callback Exception  ", e);
        }

        return WebContext.redirect("/login");
    }

    public String createRandomCharData(int length)
    {
        StringBuilder sb = new StringBuilder("TT-");
        Random rand = new Random();//随机用以下三个随机生成器
        Random randdata = new Random();
        int data = 0;
        for (int i = 0; i < length; i++)
        {
            int index = rand.nextInt(3);
            //目的是随机选择生成数字，大小写字母
            switch (index)
            {
                case 0:
                    data = randdata.nextInt(10);//仅仅会生成0~9
                    sb.append(data);
                    break;
                case 1:
                    data = randdata.nextInt(26) + 65;//保证只会产生65~90之间的整数
                    sb.append((char) data);
                    break;
                case 2:
                    data = randdata.nextInt(26) + 97;//保证只会产生97~122之间的整数
                    sb.append((char) data);
                    break;
            }
        }
        String result = sb.toString();
        _logger.info("生成的随机数兑换码为{}", result);
        return result;
    }


    public UserInfo buildUserInfo(OapiV2UserGetResponse.UserGetResponse user)
    {
        UserInfo userInfo = new UserInfo();
        // 手机号 作为用户名
        userInfo.setUsername(user.getMobile());//鐧诲綍鍚�
        userInfo.setNickName(user.getName());//鐢ㄦ埛鍚�
        userInfo.setDisplayName(user.getName());//鐢ㄦ埛鍚�
        userInfo.setFormattedName(user.getName());//鐢ㄦ埛鍚�
        userInfo.setAssociateId(user.getUserid()); // 第三方用户的id

        userInfo.setEmail(user.getEmail());
        userInfo.setEntryDate(new DateTime(user.getHiredDate()).toString(DateTimeFormat.forPattern("yyyy-MM-dd")));
        userInfo.setMobile(user.getMobile());//鎵嬫満
        userInfo.setDepartmentId(user.getDeptIdList().get(0) + "");
        userInfo.setJobTitle(user.getTitle());//鑱屽姟
        userInfo.setWorkEmail(user.getOrgEmail());//宸ヤ綔閭欢
        userInfo.setWorkPhoneNumber(user.getTelephone());//鍏徃鐢佃瘽
        userInfo.setWorkOfficeName(user.getWorkPlace());//鍔炲叕瀹�
        userInfo.setDescription(user.getRemark());//澶囨敞

        userInfo.setPassword("123456");
        userInfo.setPasswordSetType(ConstantsPasswordSetType.PASSWORD_NORMAL);
        userInfo.setStatus(1);
//        userInfo.setDepartmentId(user.getd);
//        userInfo.setDepartment(dept.getName());

        return userInfo;
    }

    public boolean socialBind(SocialsAssociate socialsAssociate)
    {
        if (null == socialsAssociate)
        {
            return false;
        }

        socialsAssociate.setSocialUserInfo(accountJsonString);
        socialsAssociate.setUserId(WebContext.getUserInfo().getId());
        socialsAssociate.setUsername(WebContext.getUserInfo().getUsername());
        //socialsAssociate.setAccessToken(JsonUtils.object2Json(accessToken));
        //socialsAssociate.setExAttribute(JsonUtils.object2Json(accessToken.getResponseObject()));
        _logger.debug("Social Bind : " + socialsAssociate);
        this.socialsAssociateService.delete(socialsAssociate);
        this.socialsAssociateService.insert(socialsAssociate);
        return true;
    }

    public boolean socialSignOn(SocialsAssociate socialsAssociate)
    {

        socialsAssociate = this.socialsAssociateService.get(socialsAssociate);

        _logger.debug("Loaded SocialSignOn Socials Associate : " + socialsAssociate);

        if (null == socialsAssociate)
        {
            WebContext.getRequest().getSession().setAttribute(
                    WebAttributes.AUTHENTICATION_EXCEPTION,
                    new BadCredentialsException(WebContext.getI18nValue("login.error.social"))
            );
            return false;
        }

        _logger.debug("Social Sign On from {} mapping to user {}",
                socialsAssociate.getProvider(), socialsAssociate.getUsername());

        LoginCredential loginCredential = new LoginCredential(
                socialsAssociate.getUsername(), "", ConstantsLoginType.SOCIALSIGNON);
        loginCredential.setProvider(this.socialSignOnProvider.getProviderName());
        authenticationProvider.authentication(loginCredential, true);
        //socialsAssociate.setAccessToken(JsonUtils.object2Json(this.accessToken));
        socialsAssociate.setSocialUserInfo(accountJsonString);
        //socialsAssociate.setExAttribute(JsonUtils.object2Json(accessToken.getResponseObject()));

        this.socialsAssociateService.update(socialsAssociate);
        return true;
    }
}
