package com.liquidnet.service.adam.controller;

import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.dypnsapi.model.v20170525.GetMobileRequest;
import com.aliyuncs.dypnsapi.model.v20170525.GetMobileResponse;
import com.aliyuncs.exceptions.ClientException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.liquidnet.common.cache.redis.util.RedisUtil;
import com.liquidnet.common.cache.redisson.util.RedisLockUtil;
import com.liquidnet.common.sms.constant.SmsEnum;
import com.liquidnet.common.sms.processor.SmsProcessor;
import com.liquidnet.commons.lang.constant.LnsEnum;
import com.liquidnet.commons.lang.core.JwtValidator;
import com.liquidnet.commons.lang.util.*;
import com.liquidnet.service.adam.constant.AdamRedisConst;
import com.liquidnet.service.adam.constant.AdamWechatConst;
import com.liquidnet.service.adam.dto.AdamThirdPartParam;
import com.liquidnet.service.adam.dto.vo.AdamLoginInfoVo;
import com.liquidnet.service.adam.dto.vo.AdamUserInfoVo;
import com.liquidnet.service.adam.service.AdamRdmService;
import com.liquidnet.service.adam.service.IAdamUserService;
import com.liquidnet.service.base.ErrorMapping;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.base.UserPathDto;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.util.DigestUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static com.liquidnet.service.adam.constant.AdamRedisConst.LOCK_KEY_SMS_CODE_MOBILE;

@ApiSupport(order = 10010)
@Api(tags = "用户登录")
@Slf4j
@Validated
@RestController
@RequestMapping("")
public class AdamLoginController {
    @Autowired
    Environment env;
    @Autowired
    RedisUtil redisUtil;
    @Autowired
    JwtValidator jwtValidator;
    @Autowired
    DefaultAcsClient defaultAcsClient;
    @Autowired
    AdamRdmService adamRdmService;
    @Autowired
    IAdamUserService adamUserService;
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Autowired
    SmsProcessor smsProcessor;

    @Value("${liquidnet.reviewer.app-login.mobile}")
    private String reviewMobile;
    private static final String PHP_API_SMS_CODE_SEND = "/smsCode";
    private static final String PHP_API_SMS_CODE_VALID = "/smsValidation";


//    @GetMapping(value = {"gen"})
//    public void genID() {
//        log.debug("-nextSnowId:{}", IDGenerator.nextSnowId());
//        log.debug("nextMilliId:{}", IDGenerator.nextMilliId());
//        log.debug("nextMilliId:{}", IDGenerator.nextTimeId());
//        log.debug("nextMilliId:{}", IDGenerator.get32UUID());
//
//
//        ObjectNode smsNode = JsonUtils.OM().createObjectNode();
//        smsNode.put("code", "123456");
//        smsProcessor.aliyunDysmsSend("13753596360",
//                SmsEnum.ADSignName.正在现场.name(), SmsEnum.ADTemplate.SMS_109535335.name(),
//                smsNode.toString());
//
//
//
//        ArrayNode smsBatchPhones = JsonUtils.OM().createArrayNode(),
//                smsBatchSignNames = JsonUtils.OM().createArrayNode(),
//                smsBatchTemplateParams = JsonUtils.OM().createArrayNode();
//        ObjectNode smsBatchTemplateParam = JsonUtils.OM().createObjectNode();
//
//        smsBatchPhones.add("13753596360");
//        smsBatchPhones.add("17701223310");
//
//        smsBatchSignNames.add(SmsEnum.ADSignName.正在现场.name());
//        smsBatchSignNames.add(SmsEnum.ADSignName.正在现场.name());
//
//        smsBatchTemplateParam.put("userName", "张某人");
//        smsBatchTemplateParam.put("aName", "大美");
//        smsBatchTemplateParam.put("time", "2021-07-10");
//        smsBatchTemplateParam.put("siteName", "北京");
//        smsBatchTemplateParam.put("url", "show");
//
//        smsBatchTemplateParams.add(smsBatchTemplateParam.deepCopy());
//
//        smsBatchTemplateParam.put("userName", "张某人");
//        smsBatchTemplateParam.put("aName", "大美2");
//        smsBatchTemplateParam.put("time", "2021-07-11");
//        smsBatchTemplateParam.put("siteName", "天津");
//        smsBatchTemplateParam.put("url", "show2");
//        smsBatchTemplateParams.add(smsBatchTemplateParam);
//
//        smsProcessor.aliyunDysmsSendBatch(smsBatchPhones.toPrettyString(), smsBatchSignNames.toString(), SmsEnum.ADTemplate.SMS_109535335.name(), smsBatchTemplateParams.toString());
//    }

    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "发送验证码")
    @ApiImplicitParams({
            @ApiImplicitParam(type = "form", required = true, dataType = "String", name = "mobile", value = "手机号"),
    })
    @GetMapping(value = {"send"})
    public ResponseDto<Object> sendSms(@Pattern(regexp = "\\d{11}", message = "手机号格式有误") @RequestParam String mobile) {
        log.debug("send to mobile:{}", mobile);
        if (RedisLockUtil.tryLock(LOCK_KEY_SMS_CODE_MOBILE + mobile, 1, 5)) {
//            Map<String, Object> respMap = null;
//            String respStr = null;
//            try {
//                LinkedMultiValueMap<String, String> paramsMap = new LinkedMultiValueMap<>();
//                paramsMap.add("mobile", mobile);
//
//                long s = System.currentTimeMillis();
//                respStr = HttpUtil.postToPhpApi(env.getProperty("liquidnet.url-service.url") + PHP_API_SMS_CODE_SEND, paramsMap);
//                log.debug("###PHP.API[{}].RESP[{}]", PHP_API_SMS_CODE_SEND, respStr);
//                log.debug("#PHP.API耗时:{}ms", System.currentTimeMillis() - s);
//                respMap = JsonUtils.fromJson(respStr, Map.class);
//            } catch (Exception e) {
//                RedisLockUtil.unlock(LOCK_KEY_USMS_MOBILE + mobile);
//                if (e instanceof HttpClientErrorException) {
//                    log.error("PHP.API验证码发送异常[mobile:{},respStr:{},ex:{}]", mobile, respStr, e.getLocalizedMessage());
//                    HttpClientErrorException ex = (HttpClientErrorException) e;
//                    JsonNode exBody = JsonUtils.fromJson(ex.getResponseBodyAsString(), JsonNode.class);
//                    return ResponseDto.failure("10003", exBody.get("message").asText());
//                }
//                log.error("PHP.API验证码发送异常[mobile:{},respStr:{}]", mobile, respStr, e);
//                return ResponseDto.failure(ErrorMapping.get("10003"));
//            }
//            if (!CollectionUtils.isEmpty(respMap) && StringUtils.equalsIgnoreCase("OK", (String) respMap.get("message"))) {
//                return ResponseDto.success();
//            } else {
//                RedisLockUtil.unlock(LOCK_KEY_USMS_MOBILE + mobile);
//                log.warn("PHP.API验证码发送失败[mobile:{},respStr:{}]", mobile, respStr);
//                return ResponseDto.failure(ErrorMapping.get("10003"));
//            }

            String smsCode = RandomStringUtils.randomNumeric(6);
//            SmsMessage smsMessage = SmsMessage.builder().setPhone(mobile).setSignName(SmsEnum.ADSignName.正在现场.name())
//                    .setTemplateCode(SmsEnum.ADTemplate.SMS_109535335.name())
//                    .setTemplateParam("code", smsCode);
//            rabbitTemplate.convertAndSend(MQConst.EX_LNS_SMS_SENDER, MQConst.RK_SMS_CODE, smsMessage.toJson());
            ObjectNode msgNode = JsonUtils.OM().createObjectNode();
            msgNode.put("code", smsCode);
            boolean sendRst = smsProcessor.send(mobile, SmsEnum.ADSignName.M02.getVal(), SmsEnum.ADTemplate.SMS_109535335.name(), msgNode.toString());
            if (sendRst) {
                adamRdmService.setSmsCodeByMobile(mobile, smsCode);

                return ResponseDto.success();
            }
            return ResponseDto.failure(ErrorMapping.get("10002"));
        } else {
            return ResponseDto.failure(ErrorMapping.get("10000"));
        }
    }

    @ApiOperationSupport(order = 3)
    @ApiOperation(value = "手机验证码登录")
    @ApiImplicitParams({
            @ApiImplicitParam(type = "body", required = true, dataType = "String", name = "mobile", value = "手机号"),
            @ApiImplicitParam(type = "body", required = true, dataType = "String", name = "code", value = "验证码"),
    })
    @PostMapping(value = {"login/sms"})
    public ResponseDto<AdamLoginInfoVo> loginBySms(@Pattern(regexp = "\\d{11}", message = "手机号格式有误")
                                                   @RequestParam String mobile,
                                                   @Pattern(regexp = "\\d{6}", message = "验证码格式有误")
                                                   @RequestParam String code) {
        log.debug("mobile:{},code:{}", mobile, code);
        ResponseDto checkSmsCodeDto = this.checkSmsCode(mobile, code);
        if (!checkSmsCodeDto.isSuccess()) return checkSmsCodeDto;

        String uid = adamRdmService.getUidByMobile(mobile);
        boolean toRegister = StringUtils.isEmpty(uid);

        AdamUserInfoVo userInfoVo;
        if (toRegister) {
            userInfoVo = adamUserService.register(mobile);
            if (null == userInfoVo) {
                return ResponseDto.failure(ErrorMapping.get("10000"));
            }
        } else {
            userInfoVo = adamRdmService.getUserInfoVoByUid(uid);
        }

        AdamLoginInfoVo loginInfoVo = AdamLoginInfoVo.getNew();
        if (!toRegister) {
//            loginInfoVo.setRealNameInfo(adamRdmService.getRealInfoVoByUid(userInfoVo.getUid()));
//            loginInfoVo.setThirdPartInfo(adamRdmService.getThirdPartVoListByUid(userInfoVo.getUid()));
            loginInfoVo.setUserMemberVo(adamRdmService.getUserMemberVoByUid(userInfoVo.getUid()));
        }
//        loginInfoVo.setMemberVo(adamRdmService.getMemberSimpleVo());
        loginInfoVo.setUserInfo(userInfoVo);
        loginInfoVo.setToken(this.ssoProcess(userInfoVo));
        loginInfoVo.getUserInfo().setMobile(SensitizeUtil.custom(userInfoVo.getMobile(), 3, 4));
        log.info(UserPathDto.setData(toRegister ? "注册" : "登录", ServletUtils.getRequest().getParameterMap(), loginInfoVo));
        return ResponseDto.success(loginInfoVo);
    }

    @ApiOperationSupport(order = 4)
    @ApiOperation(value = "手机号一键登录")
    @ApiImplicitParams({
            @ApiImplicitParam(type = "form", required = true, dataType = "String", name = "accessToken", value = "访问令牌"),
    })
    @PostMapping(value = {"login/mobile"})
    public ResponseDto<AdamLoginInfoVo> loginByMobile(@NotBlank(message = "访问令牌不能为空") @RequestParam String accessToken) {
        log.debug("login by mobile access token:{}", accessToken);
        String mobile = this.getMobile(accessToken);
        if (StringUtils.isEmpty(mobile)) return ResponseDto.failure(ErrorMapping.get("10005"));

        String uid = adamRdmService.getUidByMobile(mobile);
        boolean toRegister = StringUtils.isEmpty(uid);

        AdamUserInfoVo userInfoVo;
        if (toRegister) {
            userInfoVo = adamUserService.register(mobile);
            if (null == userInfoVo) {
                return ResponseDto.failure(ErrorMapping.get("10000"));
            }
        } else {
            userInfoVo = adamRdmService.getUserInfoVoByUid(uid);
        }

        AdamLoginInfoVo loginInfoVo = AdamLoginInfoVo.getNew();
        if (!toRegister) {
//            loginInfoVo.setRealNameInfo(adamRdmService.getRealInfoVoByUid(userInfoVo.getUid()));
//            loginInfoVo.setThirdPartInfo(adamRdmService.getThirdPartVoListByUid(userInfoVo.getUid()));
            loginInfoVo.setUserMemberVo(adamRdmService.getUserMemberVoByUid(userInfoVo.getUid()));
        }
//        loginInfoVo.setMemberVo(adamRdmService.getMemberSimpleVo());
        loginInfoVo.setUserInfo(userInfoVo);
        loginInfoVo.setToken(this.ssoProcess(userInfoVo));
        loginInfoVo.getUserInfo().setMobile(SensitizeUtil.custom(userInfoVo.getMobile(), 3, 4));
        log.info(UserPathDto.setData(toRegister ? "注册" : "登录", ServletUtils.getRequest().getParameterMap(), loginInfoVo));
        return ResponseDto.success(loginInfoVo);
    }

    @ApiOperationSupport(order = 5)
    @ApiOperation(value = "第三方账号登录")
    @PostMapping(value = {"login/tpa"})
    public ResponseDto<AdamLoginInfoVo> loginByTpa(@Valid @RequestBody AdamThirdPartParam parameter) {
        log.debug("login by tpa:{}", JsonUtils.toJson(parameter));
        boolean toRegister = false;
        AdamLoginInfoVo loginInfoVo = AdamLoginInfoVo.getNew();
        if (StringUtils.isEmpty(parameter.getMobile())) {
            String uid = adamRdmService.getUidByPlatformOpenId(parameter.getPlatform(), parameter.getOpenId());
            if (StringUtils.isEmpty(uid)) return ResponseDto.failure(ErrorMapping.get("10006"));

            loginInfoVo.setUserInfo(adamRdmService.getUserInfoVoByUid(uid));
//            loginInfoVo.setRealNameInfo(adamRdmService.getRealInfoVoByUid(uid));
//            loginInfoVo.setThirdPartInfo(adamRdmService.getThirdPartVoListByUid(uid));
            loginInfoVo.setUserMemberVo(adamRdmService.getUserMemberVoByUid(uid));
//            loginInfoVo.setMemberVo(adamRdmService.getMemberSimpleVo());
        } else {// 新账号注册
            ResponseDto checkSmsCodeDto = this.checkSmsCode(parameter.getMobile(), parameter.getCode());
            if (!checkSmsCodeDto.isSuccess()) {
                return checkSmsCodeDto;
            }
            AdamUserInfoVo registerUserInfo = adamUserService.register(parameter);
            if (null == registerUserInfo) {
                return ResponseDto.failure(ErrorMapping.get("10000"));
            }
            toRegister = true;
            loginInfoVo.setUserInfo(registerUserInfo);
            loginInfoVo.setThirdPartInfo(adamRdmService.getThirdPartVoListByUid(registerUserInfo.getUid()));
//            loginInfoVo.setMemberVo(adamRdmService.getMemberSimpleVo());
        }
        loginInfoVo.setToken(this.ssoProcess(loginInfoVo.getUserInfo()));
        loginInfoVo.getUserInfo().setMobile(SensitizeUtil.custom(loginInfoVo.getUserInfo().getMobile(), 3, 4));
        log.info(UserPathDto.setData(toRegister ? "注册" : "登录", ServletUtils.getRequest().getParameterMap(), loginInfoVo));
        return ResponseDto.success(loginInfoVo);
    }

    @ApiOperationSupport(order = 6)
    @ApiOperation(value = "登出")
    @PostMapping(value = {"out"})
    public void logout() {
        log.info("###logout:uid:{}\ntoken:{}", CurrentUtil.getCurrentUid(), CurrentUtil.getToken());

        redisUtil.del(jwtValidator.getSsoRedisKey().concat(CurrentUtil.getCurrentUid()));
    }

    @ApiOperationSupport(order = 7)
    @ApiOperation(value = "注销")
    @PostMapping(value = {"close"})
    public ResponseDto<Object> close() {
        log.info("###close:uid:{}", CurrentUtil.getCurrentUid());

        this.logout();

        adamUserService.close(CurrentUtil.getCurrentUid());

        return ResponseDto.success();
    }

    @ApiOperationSupport(order = 8)
    @ApiOperation(value = "时间戳")
    @GetMapping(value = {"ts"})
    public ResponseDto<Long> timestamp() {
        return ResponseDto.success(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli());
    }

    @ApiOperationSupport(order = 9)
    @ApiOperation(value = "微信小程序登录凭证校验", notes = "这里仅用于获取OPENID使用。登录凭证校验。通过 wx.login 接口获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程。更多使用方法详见 https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html")
    @GetMapping(value = {"wxa/code2session"})
    public ResponseDto<String> wxaCode2Session(@RequestParam String jsCode) {
        String openId = null, respJStr = null;
        try {
            String url = AdamWechatConst.API_URL_JS_CODE2SESSION.replace("APPID", AdamWechatConst.zhengzaiAppletAppid)
                    .replace("SECRET", AdamWechatConst.zhengzaiAppletSecret).replace("JSCODE", jsCode);
            log.debug("jsCode={},url={}", jsCode, url);
            respJStr = HttpUtil.get(url, null);
            JsonNode respJNode = JsonUtils.fromJson(respJStr, JsonNode.class), respErrcode;
            if (null == respJNode || (((respErrcode = respJNode.get("errcode")) != null) && !"0".equalsIgnoreCase(respErrcode.asText()))) {
                log.warn("WX.API调用失败[{}]", respJStr);
                return ResponseDto.success(null);
            }
            openId = respJNode.get("openid").asText();
        } catch (Exception e) {
            log.error("WX.API调用异常[jsCode:{},respJStr={}]", jsCode, respJStr, e);
        }
        log.debug("jsCode={},openid={}", jsCode, openId);
        return ResponseDto.success(openId);
    }

    @ApiOperationSupport(order = 10)
    @ApiOperation(value = "微信网站应用登录", notes = "这里仅用于获取OPENID使用。方法详见 https://developers.weixin.qq.com/doc/oplatform/Website_App/WeChat_Login/Wechat_Login.html")
    @GetMapping(value = {"wx/oauth2/access_token"})
    public ResponseDto<String> wxOauth2AccessToken(@RequestParam String code) {
        String openId = null, respJStr = null;
        try {
            String url = AdamWechatConst.API_URL_OAUTH2_ACCESS_TOKEN.replace("APPID", AdamWechatConst.zhengzaiServiceAppid)
                    .replace("SECRET", AdamWechatConst.zhengzaiServiceSecret).replace("CODE", code);
            log.debug("code={},url={}", code, url);
            respJStr = HttpUtil.get(url, null);
            JsonNode respJNode = JsonUtils.fromJson(respJStr, JsonNode.class), respErrcode;
            if (null == respJNode || (((respErrcode = respJNode.get("errcode")) != null) && !"0".equalsIgnoreCase(respErrcode.asText()))) {
                log.warn("WX.API调用失败[{}]", respJStr);
                return ResponseDto.success(null);
            }
            openId = respJNode.get("openid").asText();
        } catch (Exception e) {
            log.error("WX.API调用异常[jsCode:{},respJStr={}]", code, respJStr, e);
        }
        log.debug("code={},openid={}", code, openId);
        return ResponseDto.success(openId);
    }

    /* ---------------------------- Internal Method ---------------------------- */

    private ResponseDto checkSmsCode(String mobile, String code) {
        Integer switchGrayLoginSms = (Integer) redisUtil.get(AdamRedisConst.SWITCH_GRAY_LOGIN_SMS);
        if (null != switchGrayLoginSms) {
            if (switchGrayLoginSms == 615243) {
                if (CurrentUtil.GRAY_LOGIN_SMS_CODE.equals(code)) {
                    return ResponseDto.success();
                }
            }
            if (switchGrayLoginSms == 612543) {
                if (reviewMobile.equals(mobile) ||
                        Arrays.asList(LnsEnum.ENV.dev.name(), LnsEnum.ENV.test.name()).contains(env.getProperty(CurrentUtil.CK_ENV_ACTIVE))
                ) {
                    if (CurrentUtil.GRAY_LOGIN_SMS_CODE.equals(code)) {
                        return ResponseDto.success();
                    }
                }
            }
        }

//        LinkedMultiValueMap<String, String> paramsMap = new LinkedMultiValueMap<>();
//        paramsMap.add("mobile", mobile);
//        paramsMap.add("code", code);
//
//        String respStr = null;
//        try {
//            respStr = HttpUtil.postToPhpApi(env.getProperty("liquidnet.url-service.url") + PHP_API_SMS_CODE_VALID, paramsMap);
//            log.debug("###PHP.API[{}].RESP:{}", PHP_API_SMS_CODE_VALID, respStr);
//            Map respMap = JsonUtils.fromJson(respStr, Map.class);
//            if (!CollectionUtils.isEmpty(respMap) && StringUtils.equalsIgnoreCase("OK", (String) respMap.get("message"))) {
//                return true;
//            } else {
//                log.warn("PHP.API验证码验证失败[mobile:{},code:{},respStr:{}]", mobile, code, respStr);
//                return false;
//            }
//        } catch (Exception e) {
//            log.error("PHP.API验证码验证异常[mobile:{},code:{},respStr:{}]", mobile, code, respStr, e);
//            return false;
//        }

        String smsCodeByMobile = adamRdmService.getSmsCodeByMobile(mobile);
        if (null == smsCodeByMobile) {
            return ResponseDto.failure(ErrorMapping.get("10004"));
        }
        return smsCodeByMobile.equals(code) ? ResponseDto.success() : ResponseDto.failure(ErrorMapping.get("10004"));
    }

    private String getMobile(String accessToken) {
        try {
            GetMobileRequest request = new GetMobileRequest();
            request.setAccessToken(accessToken);

            GetMobileResponse response = defaultAcsClient.getAcsResponse(request);

            if (!Objects.isNull(response) && response.getCode().equalsIgnoreCase("OK")) {
                return response.getGetMobileResultDTO().getMobile();
            }
            log.warn("aliyun.dypns.api.response:{},{}", JsonUtils.toJson(response), accessToken);
        } catch (ClientException e) {
            log.error("aliyun.dypns.api:{}", accessToken, e);
        }
        return null;
    }

    private String ssoProcess(AdamUserInfoVo userInfoVo) {
        Map<String, Object> claimsMap = CollectionUtil.mapStringObject();
        claimsMap.put("sub", userInfoVo.getUid());
        claimsMap.put("mobile", userInfoVo.getMobile());
        claimsMap.put("nickname", userInfoVo.getNickname());
        claimsMap.put("type", "user");

        String token = jwtValidator.create(claimsMap);

        redisUtil.set(
                jwtValidator.getSsoRedisKey().concat(userInfoVo.getUid()),
                DigestUtils.md5DigestAsHex(token.getBytes(StandardCharsets.UTF_8)),
                jwtValidator.getExpireTtl() * 60
        );
        return token;
    }
}
