/* (C) 2022 YiRing, Inc. */
package com.yiring.auth.web.auth;

import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.*;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import com.yiring.auth.config.AuthConfig;
import com.yiring.auth.core.Captcha;
import com.yiring.auth.domain.log.LoginLog;
import com.yiring.auth.domain.log.LoginLogRepository;
import com.yiring.auth.domain.user.User;
import com.yiring.auth.domain.user.UserRepository;
import com.yiring.auth.param.auth.CaptchaParam;
import com.yiring.auth.param.auth.LoginParam;
import com.yiring.auth.param.auth.RegisterParam;
import com.yiring.auth.param.auth.SafeParam;
import com.yiring.auth.util.Auths;
import com.yiring.auth.vo.auth.CaptchaVo;
import com.yiring.auth.vo.auth.LoginVo;
import com.yiring.common.annotation.RateLimiter;
import com.yiring.common.core.I18n;
import com.yiring.common.core.Result;
import com.yiring.common.core.Status;
import com.yiring.common.exception.BusinessException;
import com.yiring.common.util.IpUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.data.domain.Example;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 用户认证控制器
 *
 * @author Jim
 * @version 0.1
 * 2022/1/24 14:13
 */

@Slf4j
@Validated
@ApiSupport(order = -9999)
@Tag(name = "Auth", description = "身份认证")
@RestController
@RequestMapping("/auth/")
@RequiredArgsConstructor
public class AuthController {

    final I18n i18n;
    final Auths auths;
    final Captcha captcha;
    final AuthConfig authConfig;
    final UserRepository userRepository;
    final LoginLogRepository loginLogRepository;

    @RateLimiter(time = 1, count = 1)
    @Operation(summary = "注册")
    @PostMapping(value = "register")
    public Result<String> register(@ParameterObject @Validated RegisterParam param) {
        // 检查用户名是否存在
        long count = userRepository.count(Example.of(User.builder().username(param.getUsername()).build()));
        if (count > 0) {
            throw BusinessException.i18n("Code.100000");
        }

        // 检查手机号是否存在
        count = userRepository.count(Example.of(User.builder().mobile(param.getMobile()).build()));
        if (count > 0) {
            throw BusinessException.i18n("Code.100001");
        }

        // 检查邮箱是否存在
        if (StrUtil.isNotBlank(param.getEmail())) {
            count = userRepository.count(Example.of(User.builder().email(param.getEmail()).build()));
            if (count > 0) {
                throw BusinessException.i18n("Code.100002");
            }
        }

        // 构建用户信息写入数据库
        User user = User
            .builder()
            .avatar(param.getAvatar())
            .mobile(param.getMobile())
            .realName(param.getRealName())
            .username(param.getUsername())
            .password(SaSecureUtil.sha256(param.getPassword()))
            .disabled(param.getDisabled())
            .build();
        userRepository.saveAndFlush(user);
        return Result.ok();
    }

    @RateLimiter(count = 3)
    @Operation(summary = "登录")
    @PostMapping("login")
    public Result<LoginVo> login(
        @ParameterObject @Validated LoginParam param,
        @ParameterObject CaptchaParam captchaParam,
        HttpServletRequest request
    ) {
        // 获取登录 IP
        // String ip = "222.244.92.58";
        String ip = IpUtil.getClientIp(request);
        // 获取登录 User-Agent
        String ua = request.getHeader("User-Agent");
        // 构建登录日志
        LoginLog loginLog = LoginLog
            .builder()
            .account(param.getAccount())
            .ip(ip)
            .location(IpUtil.getIpRegion(ip))
            .ua(ua)
            .build();

        // 尝试解析 User-Agent
        UserAgent userAgent = UserAgentUtil.parse(ua);
        if (Objects.nonNull(userAgent)) {
            // 获取操作系统
            OS os = userAgent.getOs();
            if (Objects.nonNull(os)) {
                loginLog.setOs(os.toString());
            }

            // 获取浏览器
            Browser browser = userAgent.getBrowser();
            if (Objects.nonNull(browser)) {
                loginLog.setBrowser(browser.toString());
            }

            // 获取平台
            Platform platform = userAgent.getPlatform();
            if (Objects.nonNull(platform)) {
                loginLog.setPlatform(platform.toString());
            }
        }

        try {
            // 校验图形验证码
            if (authConfig.isCaptcha() || StrUtil.isNotBlank(captchaParam.getCaptchaKey())) {
                if (!captcha.verify(captchaParam.getCaptchaKey(), captchaParam.getCaptchaCode())) {
                    throw BusinessException.i18n("Code.100006");
                }
            }

            // 查询用户信息是否匹配
            User user = userRepository.findByAccount(param.getAccount());
            if (user == null) {
                throw BusinessException.i18n("Code.100003");
            }

            // 检查用户是否已被删除
            if (Boolean.TRUE.equals(user.getDeleted())) {
                throw BusinessException.i18n("Code.100004");
            }

            // 检查用户是否被允许登录
            if (Boolean.TRUE.equals(user.getDisabled())) {
                throw BusinessException.i18n("Code.100005");
            }

            // 检查密码
            String cps = SaSecureUtil.sha256(param.getPassword());
            if (!cps.equals(user.getPassword())) {
                throw BusinessException.i18n("Code.100003");
            }

            // 更新用户信息
            user.setLastLoginIp(ip);
            user.setLastLoginTime(LocalDateTime.now());
            userRepository.saveAndFlush(user);

            // 登录
            StpUtil.login(user.getId());

            // 登录日志状态
            loginLog.setStatus(true);
            loginLog.setMsg(i18n.get(Status.OK.getReasonPhrase()));

            // 构建用户所需信息
            LoginVo vo = LoginVo.builder().userId(user.getId()).token(StpUtil.getTokenValue()).build();
            return Result.ok(vo);
        } catch (Exception e) {
            // 登录日志状态
            loginLog.setStatus(false);
            loginLog.setMsg(e.getMessage());
            throw e;
        } finally {
            // 保存登录日志
            loginLogRepository.saveAndFlush(loginLog);
        }
    }

    @Operation(summary = "检查登录")
    @GetMapping("valid")
    public Result<Boolean> valid() {
        return Result.ok(StpUtil.isLogin());
    }

    @RateLimiter(count = 3)
    @Operation(summary = "登出")
    @GetMapping("logout")
    public Result<String> logout() {
        StpUtil.logout();
        return Result.ok();
    }

    /**
     * 二次安全校验，搭配 @SaCheckSafe 实现对关键数据不可逆操作前的二次确认
     * 默认安全时间: 120s
     *
     * @param param 用户密码
     * @link { <a href="https://sa-token.dev33.cn/doc.html#/up/safe-auth">...</a> }
     */
    @RateLimiter(count = 3)
    @SaCheckLogin
    @Operation(summary = "安全验证")
    @GetMapping("safe")
    public Result<String> safe(@ParameterObject @Validated SafeParam param) {
        User user = auths.getLoginUser();
        if (SaSecureUtil.sha256(param.getPassword()).equals(user.getPassword())) {
            StpUtil.openSafe(360);
            return Result.ok();
        }

        return Result.no();
    }

    @Operation(summary = "生成图形验证码")
    @GetMapping("captcha")
    public Result<CaptchaVo> captcha() {
        CaptchaVo vo = captcha.create();
        return Result.ok(vo);
    }
}
