package com.yiboshi.science.rest.v1;

import cn.hutool.core.util.IdUtil;
import com.google.common.hash.Hashing;
import com.wf.captcha.base.Captcha;
import com.yiboshi.arch.base.ResponseDataModel;
import com.yiboshi.arch.exception.BusinessException;
import com.yiboshi.science.api.ApiHttpRequest;
import com.yiboshi.science.config.annotation.Anonymous;
import com.yiboshi.science.config.bean.SystemProperties;
import com.yiboshi.science.config.security.SecurityUser;
import com.yiboshi.science.config.security.SecurityUserHolder;
import com.yiboshi.science.config.security.loginUser;
import com.yiboshi.science.config.verification.VerificationCodeEnum;
import com.yiboshi.science.config.verification.VerificationCodeProperties;
import com.yiboshi.science.entity.AuthUser;
import com.yiboshi.science.entity.ComPerson;
import com.yiboshi.science.entity.ComUnit;
import com.yiboshi.science.entity.SystemUser;
import com.yiboshi.science.enumeration.CommonEnum;
import com.yiboshi.science.param.dto.CheckUserDTO;
import com.yiboshi.science.param.dto.SMSParameterDTO;
import com.yiboshi.science.param.dto.UserDTO;
import com.yiboshi.science.param.dto.UserMenuDTO;
import com.yiboshi.science.service.*;
import com.yiboshi.science.utils.RedisUtils;
import com.yiboshi.science.utils.StringUtil;
import com.yiboshi.science.utils.SystemSetKey;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * /**
 *
 * @author wxl
 * @date
 */
@Slf4j
@RestController
@RequestMapping("/v1/science-admin/auth")
@RequiredArgsConstructor
@Api(tags = "authentication", description = "系统授权接口")
public class AuthorizationController {

    @Autowired
    protected HttpServletRequest request;

    @Autowired
    private SystemMenuService systemMenuService;

    @Autowired
    private SystemUserService systemUserService;

    @Autowired
    private ComPersonService comPersonService;

    @Autowired
    private ComUnitService comUnitService;

    @Autowired
    private TokenEndpoint tokenEndpoint;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private ComSendingRecordService comSendingRecordService;

    @Resource
    private VerificationCodeProperties verificationCodeProperties;

    private final RedisUtils redisUtils;

    @Autowired
    private SystemSetService systemSetService;

    @Autowired
    private ApiHttpRequest apiHttpRequest;

    @Autowired
    private LogsLoginService login;

    @Autowired
    private SystemProperties properties;

    @ApiOperation(value = "获取验证码", httpMethod = "GET", notes = "获取验证码")
    @RequestMapping(value = "/getCode")
    @GetMapping
    @Anonymous
    public ResponseDataModel<Object> getCode() {
        // 获取运算的结果
        Captcha captcha = verificationCodeProperties.getCaptcha();
        String uuid = IdUtil.simpleUUID();
        // 当验证码类型为 arithmetic时且长度 >= 2 时，captcha.text()的结果有几率为浮点型
        String captchaValue = captcha.text();
        if (captcha.getCharType() - 1 == VerificationCodeEnum.arithmetic.ordinal() && captchaValue.contains(".")) {
            captchaValue = captchaValue.split("\\.")[0];
        }
        // 保存
        redisUtils.set(uuid, captchaValue, verificationCodeProperties.getVerificationCode().getExpiration(), TimeUnit.MINUTES);
        // 验证码信息
        Map<String, Object> imgResult = new HashMap<String, Object>(2) {
            {
                put("img", captcha.toBase64());
                put("uuid", uuid);
                put("systemName", properties.getSystemName());
            }
        };
        return ResponseDataModel.ok(imgResult);
    }

    @ApiOperation(value = "登录授权", httpMethod = "POST", notes = "登录授权")
    @PostMapping(value = "/login")
    @Anonymous
    public ResponseDataModel<OAuth2AccessToken> login(@RequestBody AuthUser authUser) throws Exception {
        if (Objects.isNull(authUser.getUuid())) {
            throw new BusinessException("参数错误！");
        }
        String value = (String) redisUtils.get(authUser.getUuid());
        if (StringUtils.isBlank(value)) {
            throw new BusinessException("验证码错误或已过期！");
        }
        if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(value)) {
            throw new BusinessException("验证码错误或已过期");
        }
        //创建客户端信息,客户端信息可以写死进行处理，因为Oauth2密码模式，客户端双信息必须存在，所以伪装一个
        User clientUser = new User("srp", "null", AuthorityUtils.commaSeparatedStringToAuthorityList(""));
        //生成已经认证的client
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(clientUser, null, new ArrayList<>());
        Map<String, String> parameters = new HashMap<String, String>();
        parameters.put("username", authUser.getUsername());
        //放入验证码
        parameters.put("password", authUser.getPassword());
        //授权模式为：密码模式
        parameters.put("grant_type", "password");
        //调用自带的获取token方法。
        OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(token, parameters).getBody();
        // 插入登录日志
        OAuth2Authentication auth2Authentication = tokenStore.readAuthentication(oAuth2AccessToken.getValue());
        SecurityUser user = (SecurityUser) auth2Authentication.getUserAuthentication().getPrincipal();
        login.log(user.getLoginName(), user.getUsername(), user.getPersonId(), user.getRoles(), null, user.getUnitId(), user.getUnitName());
        // 清除验证码
        redisUtils.del(authUser.getUuid());
        return ResponseDataModel.ok(oAuth2AccessToken);
    }

    @ApiOperation(value = "获取用户信息", httpMethod = "GET", notes = "获取用户信息")
    @RequestMapping(value = "/getUser")
    @GetMapping
    public ResponseDataModel<Map<String, Object>> getUser() {
        loginUser user = new loginUser();
        StringUtil.copyObj2Obj(SecurityUserHolder.getCurrentUser(), user);
        user.setType(systemSetService.getByKey(SystemSetKey.SysProjectType));
        if(!user.getType().equals("3")){
            user.setSystemType(systemSetService.getByKey(SystemSetKey.SysProjectType));
        }
        if (SecurityUserHolder.getRoles().contains(CommonEnum.systemRole.personal.getCode().toString())) {
            ComPerson  person = comPersonService.getById(SecurityUserHolder.getPersonId());
            user.setComplete(comPersonService.isComplete(person));
        }
        if (SecurityUserHolder.getRoles().contains(CommonEnum.systemRole.unit.getCode().toString())) {
            ComUnit comUnit = comUnitService.getById(SecurityUserHolder.getUnitId());
            user.setUnitComplete(comUnitService.isComplete(comUnit));
        }
//      List<UserMenuDTO> menuList = systemMenuService.findByRoleId(SecurityUserHolder.getRoles());
        Map<String, Object> userInfo = new HashMap<String, Object>(3) {
            {
                put("userInfo", user);
//              put("menuList", menuList);
            }
        };
        return ResponseDataModel.ok(userInfo);
    }

    @ApiOperation(value = "获取菜单", httpMethod = "GET", notes = "获取菜单")
    @RequestMapping(value = "/getMenu")
    @GetMapping
    public ResponseDataModel<List<UserMenuDTO>> getMenu(Integer systemType) {
        List<UserMenuDTO> menuList = systemMenuService.findByRoleId(SecurityUserHolder.getRoles(), systemType);
        return ResponseDataModel.ok(menuList);
    }

    @ApiOperation(value = "验证手机号", httpMethod = "GET", notes = "验证手机号")
    @RequestMapping(value = "/checkMobile")
    @GetMapping
    @Anonymous
    public ResponseDataModel<Boolean> checkMobile(String mobile, String type) {
        Boolean checkState = false;
        if (type.equals("register")) {
            if (!comPersonService.isMobileExist(mobile)) {
                checkState = true;
            }
        } else if (type.equals("reset")) {
            if (comPersonService.isMobileExist(mobile)) {
                checkState = true;
            }
        }
        return ResponseDataModel.ok(checkState);
    }

    @ApiOperation(value = "验证用户名", httpMethod = "GET", notes = "验证手机号")
    @RequestMapping(value = "/checkUser")
    @GetMapping
    @Anonymous
    public ResponseDataModel<Boolean> checkUser(String username) {
        Boolean checkState = false;
        if (systemUserService.isExist(username)) {
            checkState = true;
        }
        return ResponseDataModel.ok(checkState);
    }

    @ApiOperation(value = "验证证件号", httpMethod = "GET", notes = "验证手机号")
    @RequestMapping(value = "/checkUserNameById")
    @GetMapping
    @Anonymous
    public ResponseDataModel<Boolean> checkUserNameById(String username, String id) {
        return ResponseDataModel.ok(systemUserService.checkUserNameById(username, id));
    }

    @ApiOperation(value = "验证证件号", httpMethod = "GET", notes = "验证手机号")
    @RequestMapping(value = "/checkCertId")
    @GetMapping
    @Anonymous
    public ResponseDataModel<Boolean> checkCertId(String certId) {
        return ResponseDataModel.ok(comPersonService.isCertIdExist(certId, null));
    }

    @ApiOperation(value = "验证证件号", httpMethod = "GET", notes = "验证手机号")
    @RequestMapping(value = "/checkCertIdById")
    @GetMapping
    @Anonymous
    public ResponseDataModel<Boolean> checkCertIdById(String certId, String id) {
        return ResponseDataModel.ok(comPersonService.isCertIdExist(certId, id));
    }


    @ApiOperation(value = "发送验证码", httpMethod = "GET", notes = "发送验证码")
    @RequestMapping(value = "/sendVerificationCode")
    @GetMapping
    @Anonymous
    public ResponseDataModel<String> sendVerificationCode(String uuid, String code, String mobile) {
        if (uuid == null || uuid.equals(""))
            throw new BusinessException("参数错误！");
        // 查询验证码
        String value = (String) redisUtils.get(uuid);
        if (StringUtils.isBlank(value)) {
            throw new BusinessException("验证码错误或已过期！");
        }
        if (StringUtils.isBlank(code) || !code.equalsIgnoreCase(value)) {
            throw new BusinessException("验证码错误或已过期");
        }
        // 清除验证码
        redisUtils.del(uuid);
        long time = System.currentTimeMillis();
        String sign = Hashing.sha256().hashString(mobile + time, StandardCharsets.UTF_8).toString();
        SMSParameterDTO sms = new SMSParameterDTO();
        sms.setMobile(mobile);
        sms.setAppId(4);
        sms.setTTypeId(42);
        sms.setSmsType(1);
        sms.setCode(null);
        sms.setTimestamp(time);
        sms.setSign(sign);
        sms.setParamMap(null);
        if (apiHttpRequest.SMSSending(sms)) {
            comSendingRecordService.record(mobile, 1, 1);
            return ResponseDataModel.ok("success");
        } else {
            comSendingRecordService.record(mobile, 2, 1);
            throw new BusinessException("短信发送失败！");
        }
    }

    @ApiOperation(value = "重置验证", httpMethod = "POST", notes = "重置验证")
    @PostMapping
    @RequestMapping(value = "/resetCheck")
    @Anonymous
    public ResponseDataModel<String> resetCheck(String vCode, String mobile) throws Exception {
        if (!apiHttpRequest.SMSCheck(mobile, vCode))
            throw new BusinessException("验证码错误或已过期！");
        String uuid = IdUtil.simpleUUID();
        ComPerson comPerson = new ComPerson();
        comPerson.setMobile(mobile);
        comPerson = comPersonService.getEntity(comPerson);
        if (null == comPerson) {
            throw new BusinessException("手机号不存在！");
        }
        redisUtils.set(uuid, comPerson.getId(), 2, TimeUnit.MINUTES);
        return ResponseDataModel.ok(uuid);
    }

    @ApiOperation(value = "重置密码", httpMethod = "POST", notes = "重置密码")
    @PostMapping
    @RequestMapping(value = "/resetPwd")
    @Anonymous
    public ResponseDataModel<String> resetPwd(@Validated @RequestBody CheckUserDTO user) throws Exception {
        // 查询验证码
        String value = (String) redisUtils.get(user.getUuid());
        if (StringUtils.isBlank(value)) {
            redisUtils.del(user.getUuid());
            throw new BusinessException("操作超时，请重新获取验证码！");
        }
        SystemUser systemUser = systemUserService.getByPersonId(value);
        if (null == systemUser) {
            redisUtils.del(user.getUuid());
            throw new BusinessException("操作超时，请重新获取验证码！");
        }
        // 清除验证码
        redisUtils.del(user.getUuid());
        SystemUser newUser = new SystemUser();
        newUser.setId(systemUser.getId());
        newUser.setPassword(user.getNewPassword());
        return ResponseDataModel.ok(systemUserService.update(newUser));
    }

    @ApiOperation(value = "修改密码", httpMethod = "POST", notes = "修改密码")
    @PostMapping
    @RequestMapping(value = "/updatePwd")
    public ResponseDataModel<String> updatePwd(@Validated @RequestBody UserDTO user) throws Exception {
        user.setUserId(SecurityUserHolder.getUserId());
        return ResponseDataModel.ok(systemUserService.updatePwd(user));
    }

    @ApiOperation(value = "退出登录", httpMethod = "GET", notes = "退出登录")
    @RequestMapping(value = "/logout")
    @GetMapping
    @Anonymous
    public ResponseDataModel<String> logout() {
        String token = SecurityUserHolder.getToken();
        if (Objects.nonNull(token)) {
            OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(token);
            if (oAuth2AccessToken != null) {
                tokenStore.removeAccessToken(oAuth2AccessToken);
                OAuth2RefreshToken oAuth2RefreshToken = oAuth2AccessToken.getRefreshToken();
                tokenStore.removeRefreshToken(oAuth2RefreshToken);
                tokenStore.removeAccessTokenUsingRefreshToken(oAuth2RefreshToken);
            }
        }
        return ResponseDataModel.ok("ok");
    }
}
