package com.izouma.jmrh.web;

import cn.licoy.encryptbody.annotation.decrypt.DecryptBody;
import cn.licoy.encryptbody.annotation.encrypt.DESEncryptBody;
import cn.licoy.encryptbody.annotation.encrypt.EncryptBody;
import cn.licoy.encryptbody.enums.DecryptBodyMethod;
import cn.licoy.encryptbody.enums.EncryptBodyMethod;
import com.izouma.jmrh.domain.LoginLog;
import com.izouma.jmrh.domain.User;
import com.izouma.jmrh.exception.AuthenticationException;
import com.izouma.jmrh.exception.BusinessException;
import com.izouma.jmrh.repo.LoginLogRepo;
import com.izouma.jmrh.repo.UserRepo;
import com.izouma.jmrh.security.Authority;
import com.izouma.jmrh.security.JwtTokenUtil;
import com.izouma.jmrh.security.JwtUserDetailsService;
import com.izouma.jmrh.security.JwtUserFactory;
import com.izouma.jmrh.service.UserService;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Slf4j
@AllArgsConstructor
@RestController
@RequestMapping("/auth")
@EncryptBody(EncryptBodyMethod.AES)
@DecryptBody(DecryptBodyMethod.AES)
public class AuthenticationController {
    private AuthenticationManager authenticationManager;
    private JwtTokenUtil          jwtTokenUtil;
    private JwtUserDetailsService userDetailsService;
    private UserService           userService;
    private LoginLogRepo          loginLogRepo;
    private UserRepo              userRepo;
    private CacheManager          cacheManager;

    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(10);

    @PostMapping("/registerByMail")
    public String registerByMail(@RequestParam String mail, @RequestParam String username, @RequestParam String password) {
        User user = userService.registerByMail(username, mail, password);
        return jwtTokenUtil.generateToken(JwtUserFactory.create(user));
    }

    @PostMapping("/loginByMail")
    public String loginByMail(String mail, String password) {
        Cache loginCache = cacheManager.getCache("loginCache");
        Cache userLockCache = cacheManager.getCache("userLockCache");
        LocalDateTime lockTime = userLockCache.get(mail, LocalDateTime.class);
        if (lockTime != null && LocalDateTime.now().isBefore(lockTime)) {
            throw new BusinessException("密码错误超过3次，请十分钟后再试");
        }

        try {
            User user = userRepo.findByEmail(mail);
            if (user == null) {
                throw new AuthenticationException("", null);
            }
            if (!new BCryptPasswordEncoder().matches(password, user.getPassword())) {
                throw new AuthenticationException("", null);
            }

            return jwtTokenUtil.generateToken(JwtUserFactory.create(user));
        } catch (Exception e) {
            log.error("loginByMail", e);
            loginLogRepo.save(LoginLog.builder()
                    .name("用户登录")
                    .type("登录")
                    .username(mail)
                    .success(false)
                    .time(LocalDateTime.now())
                    .build());
            loginCache.putIfAbsent(mail, Integer.valueOf(0));
            loginCache.put(mail, loginCache.get(mail, Integer.class) + 1);
            if (loginCache.get(mail, Integer.class) >= 3) {
                userLockCache.put(mail, LocalDateTime.now().plusMinutes(10));
            }
            scheduler.schedule(() -> {
                loginCache.putIfAbsent(mail, Integer.valueOf(0));
                loginCache.put(mail, loginCache.get(mail, Integer.class) - 1);
            }, 10, TimeUnit.MINUTES);
            throw new AuthenticationException("用户名或密码错误", e);
        }
    }

    @PostMapping("/login")
    public String loginByUserPwd(String username, String password, Integer expiration) {
        try {
            authenticate(username, password);
            final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            if (userDetails.getAuthorities().stream().noneMatch(auth -> {
                if ("ROLE_USER".equals(auth.getAuthority())) {
                    return false;
                }
                for (Authority.NAMES value : Authority.NAMES.values()) {
                    if (value.name().equals(auth.getAuthority())) {
                        return true;
                    }
                }
                return false;
            })) {
                throw new BusinessException("无权限");
            }
            loginLogRepo.save(LoginLog.builder()
                    .name("后台账号登录")
                    .type("登录")
                    .username(username)
                    .success(true)
                    .time(LocalDateTime.now())
                    .build());
            return jwtTokenUtil.generateToken(userDetails);
        } catch (Exception e) {
            log.error("loginByUserPwd", e);
            loginLogRepo.save(LoginLog.builder()
                    .name("后台账号登录")
                    .type("登录")
                    .username(username)
                    .success(false)
                    .time(LocalDateTime.now())
                    .build());
            throw new AuthenticationException("用户名或密码错误", e);
        }
    }

//    @PostMapping("/loginApp")
//    public String loginApp(String username, String password, Integer expiration) {
//        try {
//            authenticate(username, password);
//            final UserDetails userDetails = userDetailsService.loadUserByUsername(username);
//            return jwtTokenUtil.generateToken(userDetails);
//        } catch (Exception e) {
//            log.error("loginByUserPwd", e);
//            throw new AuthenticationException("用户名或密码错误", e);
//        }
//    }
//
//    @PostMapping("/phoneLogin")
//    @ApiOperation(value = "手机号登录")
//    public String phoneLogin(String phone, String code, String password) {
//        try {
//            User user = userService.loginByPhone(phone, code, password);
//            return jwtTokenUtil.generateToken(JwtUserFactory.create(user));
//        } catch (Exception e) {
//            log.error("loginByPhone", e);
//            if (e instanceof BusinessException) {
//                throw new AuthenticationException(((BusinessException) e).getError(), e);
//            } else {
//                throw new AuthenticationException(e.getMessage(), e);
//            }
//        }
//    }

//    @PostMapping("/mpLogin")
//    @ApiOperation(value = "公众号登录")
//    public String mpLogin(String code) {
//        try {
//            User user = userService.loginMp(code);
//            return jwtTokenUtil.generateToken(JwtUserFactory.create(user));
//        } catch (Exception e) {
//            log.error("loginByCode", e);
//            throw new AuthenticationException("登陆错误", e);
//        }
//    }
//
//    @PostMapping("/maLogin")
//    @ApiOperation(value = "小程序登录")
//    public String maLogin(String code) {
//        try {
//            User user = userService.loginMa(code);
//            return jwtTokenUtil.generateToken(JwtUserFactory.create(user));
//        } catch (Exception e) {
//            log.error("loginByCode", e);
//            throw new AuthenticationException("登陆错误", e);
//        }
//    }

    /**
     * Authenticates the user. If something is wrong, an {@link AuthenticationException} will be thrown
     */
    private void authenticate(String username, String password) {
        Objects.requireNonNull(username);
        Objects.requireNonNull(password);
        try {
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        } catch (DisabledException e) {
            throw new AuthenticationException("User is disabled!", e);
        } catch (BadCredentialsException e) {
            throw new AuthenticationException("Bad credentials!", e);
        }
    }
}
