package cn.edu.whu.boot.user.controllers;

import cn.edu.whu.boot.allocation.exceptions.InvalidUserRoleCodeException;
import cn.edu.whu.boot.common.domain.ResultMessage;
import cn.edu.whu.boot.common.service.CacheService;
import cn.edu.whu.boot.common.utils.user.PasswordEncoder;
import cn.edu.whu.boot.user.controllers.dtos.UserWithoutPrivateInfo;
import cn.edu.whu.boot.user.entities.Role;
import cn.edu.whu.boot.user.entities.User;
import cn.edu.whu.boot.user.services.RoleService;
import cn.edu.whu.boot.user.services.UserService;
import cn.edu.whu.boot.xml.domain.RedisKeyType;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.MailException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.util.DigestUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Positive;
import java.util.*;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/user")
@Validated
public class UserController {

    private static final Logger log = LoggerFactory.getLogger(UserController.class);

    private final String userCountCacheKey = "users:count";

    private Map<Integer, String> IdToRoleName = new HashMap<Integer, String>() {{
        put(1, "SuperAdmin");
        put(2, "Leader");
        put(3, "Admin");
        put(4, "Worker");
        put(5, "Operator");
    }};

    private Map<String, Integer> roleNameToId = new HashMap<String, Integer>() {{
        put("SuperAdmin", 1);
        put("Leader", 2);
        put("Admin", 3);
        put("Worker", 4);
        put("Operator", 5);
    }};

    private final int pageSize = 9;

    @Value("${spring.mail.username}")
    private String FROM;

    @Value("${email.subject}")
    private String emailSubject;

    private final UserService userService;

    private final RoleService roleService;

    private final JavaMailSender mailSender;

    private final CacheService cacheService;

    public UserController(UserService userService, RoleService roleService, JavaMailSender mailSender, CacheService cacheService) {
        this.userService = userService;
        this.roleService = roleService;
        this.mailSender = mailSender;
        this.cacheService = cacheService;
    }

    /**
     * 用户注册，注册成功则返回用户 ID
     *
     * @param username
     * @param password
     * @param phone
     * @param code     用于确认用户身份的邀请码
     * @throws Exception 用户名已被使用、手机号码已被使用、邀请码错误等异常
     */
    @RequestMapping("/register")
    @ResponseBody
    public ResultMessage register(@RequestParam("username")
                                  @NotBlank(message = "用户名不能为空")
                                          String username,
                                  @RequestParam("realName")
                                  @NotBlank(message = "真实姓名不能为空")
                                          String realName,
                                  @RequestParam("password")
                                  @NotBlank(message = "密码不能为空")
                                          String password,
                                  @RequestParam("phone")
                                  @Pattern(regexp = "\\d{11}", message = "非法的手机号码")
                                          String phone,
                                  @RequestParam("email")
                                  @Email(message = "邮箱格式错误")
                                          String email,
                                  @RequestParam("code")
                                  @NotBlank(message = "邀请码不能为空")
                                          String code) throws Exception {
        // 创建用户成功则返回 ID
        try {
            int numCode = Integer.parseInt(code);
            User newUser = userService.createUser(username, realName, password, phone, email, numCode);
            cacheService.delete(userCountCacheKey);
            return new ResultMessage<>(true, "注册成功", newUser.getId());
        } catch (NumberFormatException e) {
            return new ResultMessage<>(false, "验证码错误");
        } catch (InvalidUserRoleCodeException e) {
            return new ResultMessage<>(false, "无效的验证码");
        }
    }

    /**
     * 管理员在后台注册用户，用户名、真实姓名、密码和用户角色必须由用户指定
     * 邮箱和手机号码可以过后由用户补充
     *
     * @param username
     * @param realName
     * @param role
     * @param password
     */
    @RequestMapping("/new")
    @ResponseBody
    public ResultMessage registerByAdmin(@RequestParam("username")
                                         @NotBlank(message = "用户名不能为空")
                                                 String username,
                                         @RequestParam("realName")
                                         @NotBlank(message = "真实姓名不能为空")
                                                 String realName,
                                         @RequestParam("role")
                                         @NotBlank(message = "角色不能为空")
                                                 String role,
                                         @RequestParam(value = "password")
                                         @NotBlank(message = "密码不能为空")
                                                 String password) throws Exception {
        Optional<Integer> optional = Optional.ofNullable(roleNameToId.get(role));
        if (!optional.isPresent()) {
            return new ResultMessage<>(false, "不存在此类用户角色: " + role);
        }
        User user = userService.createUser(username, realName, password, optional.get());
        cacheService.delete(userCountCacheKey);
        return new ResultMessage<>(true, "注册成功", user.getId());
    }

    /**
     * 用户登录，登录成功则返回用户 ID
     * 如果用户的角色是管理人员，还会返回外业人员名单
     *
     * @param username
     * @param password
     */
    @RequestMapping("/login")
    @ResponseBody
    public ResultMessage login(@RequestParam("username")
                               @NotBlank(message = "用户名不能为空")
                                       String username,
                               @RequestParam("password")
                               @NotBlank(message = "密码不能为空")
                                       String password,
                               @RequestParam(value = "isAndroid", defaultValue = "false") boolean isAndroid) {
        // 确保之前的用户已经登出
        ensureUserLoggedOut();
        // 登录
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        subject.login(token); // 失败则抛出异常，已在 GlobalExceptionHandler 中处理
        User user = (User) subject.getPrincipal();
        if (!isAndroid && (user.getPhone() == null ||
                user.getPhone().equals("") ||
                user.getEmail() == null ||
                user.getEmail().equals(""))) {
            return new ResultMessage<>(false, "用户信息不完整", 204);
        }
        log.info(user.getUsername() + "已登录");
        Map<String, Object> result = new HashMap<>();
        result.put("id", user.getId());
        result.put("username", user.getUsername());
        result.put("role", IdToRoleName.get(user.getRole()));
        return new ResultMessage<>(true, "登录成功", result, 200);
    }

    /**
     * 确保之前的用户已经登出，避免失效的 session 的干扰
     */
    private void ensureUserLoggedOut() {
        try {
            // Get the user if one is logged in.
            Subject currentUser = SecurityUtils.getSubject();
            if (currentUser == null) {
                return;
            }
            // Log the user out and kill their session if possible.
            currentUser.logout();
            Optional.ofNullable(currentUser.getSession(false))
                    .ifPresent(org.apache.shiro.session.Session::stop);
        } catch (Exception e) {
            // Ignore all errors, as we're trying to silently
            // log the user out.
            log.error(e.getMessage());
        }
    }

    /**
     * 获取找回密码的验证码，将验证码发送到用户的注册邮箱
     *
     * @param email 用户的注册邮箱
     */
    @RequestMapping("/password_recover/authentication")
    @ResponseBody
    public ResultMessage sendRecoverPwdCode(@RequestParam("email")
                                            @Email String email) {
        Optional<User> optionalUser = userService.findByEmail(email);
        if (!optionalUser.isPresent()) {
            return new ResultMessage<>(false, "无此用户");
        }
        User user = optionalUser.get();
        // 生成四位随机数
        Random random = new Random(System.currentTimeMillis());
        int code = 1000 + random.nextInt(9000);
        // 将验证码保存在 session 中
        cacheService.set(RedisKeyType.EMAIL_AUTH_CODE + email, String.valueOf(code), 600);
        // 发送邮件
        String message = String.format("您好，您的验证码为 %d，请在 10 分钟内进行操作。", code);
        if (sendMail(user.getEmail(), message)) {
            return new ResultMessage<>(true, "验证码发送成功");
        } else {
            return new ResultMessage<>(false, "验证码发送失败");
        }
    }

    /**
     * 发送邮件
     *
     * @param to      收件人邮箱
     * @param content 邮件内容
     * @return true 则发送成功
     * @throws MailException 发送邮件时发生异常
     */
    private boolean sendMail(String to, String content) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom(FROM);
        message.setTo(to);
        message.setSubject(emailSubject);
        message.setText(content);
        try {
            mailSender.send(message);
        } catch (MailException e) {
            log.error(e.getMessage());
            return false;
        }
        return true;
    }

    /**
     * 用户找回密码
     *
     * @param code        验证码
     * @param email       注册时的邮箱
     * @param newPassword 新密码
     * @throws Exception 验证码错误、用户不存在、密码修改失败
     */
    @RequestMapping("/password_recover/action")
    @ResponseBody
    public ResultMessage recoverPassword(@RequestParam("code")
                                         @NotBlank(message = "验证码不能为空")
                                                 String code,
                                         @RequestParam("email")
                                         @Email(message = "邮箱格式错误")
                                                 String email,
                                         @RequestParam("newPassword")
                                         @NotBlank(message = "新密码不能为空")
                                                 String newPassword) throws Exception {
        String actualCode = (String) cacheService.get(RedisKeyType.EMAIL_AUTH_CODE + email);
        if (!code.equals(actualCode)) {
            return new ResultMessage<>(false, "验证码错误或已失效");
        }
        Optional<User> optionalUser = userService.findByEmail(email);
        if (!optionalUser.isPresent()) {
            return new ResultMessage<>(false, "无此用户");
        }
        User user = optionalUser.get();
        String hashed = PasswordEncoder.encode(newPassword, user.getSalt());
        user.setPassword(hashed);
        user = userService.updateUser(user);
        if (!user.getPassword().equals(hashed)) {
            throw new Exception("密码修改失败");
        } else {
            return new ResultMessage<>(true, "密码修改成功");
        }
    }

    /**
     * 修改密码
     *
     * @param username
     * @param oldPassword 原始密码
     * @param newPassword 新密码
     * @throws Exception 用户不存在、原始密码错误、密码修改失败
     */
    @RequestMapping("/password_reset")
    @ResponseBody
    public ResultMessage resetPassword(@RequestParam("username")
                                       @NotBlank(message = "用户名不能为空")
                                               String username,
                                       @RequestParam("oldPassword")
                                       @NotBlank(message = "原始密码不能为空")
                                               String oldPassword,
                                       @RequestParam("newPassword")
                                       @NotBlank(message = "新密码不能为空")
                                               String newPassword) throws Exception {
        Optional<User> optionalUser = userService.findByUsername(username);
        if (!optionalUser.isPresent()) {
            return new ResultMessage<>(false, "用户不存在");
        }
        User user = optionalUser.get();
        if (!user.getPassword().equals(PasswordEncoder.encode(oldPassword, user.getSalt()))) {
            return new ResultMessage<>(false, "原始密码错误");
        }
        String hashed = PasswordEncoder.encode(newPassword, user.getSalt());
        user.setPassword(hashed);
        user = userService.updateUser(user);
        if (!user.getPassword().equals(hashed)) {
            throw new Exception("密码修改失败");
        } else {
            return new ResultMessage<>(true, "密码修改成功");
        }
    }

    /**
     * 用户修改手机和邮箱
     *
     * @param username 用户名
     * @param phone    手机
     * @param email    邮箱
     * @throws Exception 该用户不存在
     */
    @RequestMapping("/update")
    @ResponseBody
    public ResultMessage updateEmailAndPhone(@RequestParam("username")
                                             @NotBlank
                                                     String username,
                                             @RequestParam("phone")
                                             @Pattern(regexp = "\\d{11}")
                                                     String phone,
                                             @RequestParam("email")
                                             @Email
                                                     String email) throws Exception {
        Optional<User> optional = userService.findByUsername(username);
        if (!optional.isPresent()) {
            return new ResultMessage<>(false, "不存在此用户");
        }
        User user = optional.get();
        user.setEmail(email);
        user.setPhone(phone);
        user = userService.updateUser(user);
        if (!user.getEmail().equals(email) || !user.getPhone().equals(phone)) {
            return new ResultMessage<>(false, "修改失败");
        } else {
            return new ResultMessage<>(true, "成功修改邮箱和手机");
        }
    }

    /**
     * 获取所有用户
     */
    @RequestMapping("/all")
    @ResponseBody
    public ResultMessage getAllUsers(@RequestParam("page") @Positive(message = "页码从1开始")
                                             int pageNum) {
        Object cache = cacheService.get(userCountCacheKey);
        int count;
        if (cache != null) {
            count = (Integer) cache;
        } else {
            count = userService.countAll();
            cacheService.set(userCountCacheKey, count, 24 * 60 * 60);
        }

        Map<String, Object> result = new HashMap<>();
        if (!pageInRange(pageNum, count, result)) {
            return new ResultMessage<>(false, "请求的页码大于总页数", result);
        }

        List<User> users = userService.findAllPagination(pageSize, pageNum);
        result.put("users", toUserWithoutPrivateInfo(users));
        return new ResultMessage<>(true, "用户列表获取成功", result);
    }

    /**
     * 根据用户名进行模糊查询
     */
    @RequestMapping("/search")
    @ResponseBody
    public ResultMessage searchUsersByNamePattern(@RequestParam("username") @NotBlank(message = "搜索内容不能为空")
                                                          String username,
                                                  @RequestParam("page") @Positive(message = "页码从1开始")
                                                          int pageNum) {
        int count = userService.countLikeUsername(username);
        Map<String, Object> result = new HashMap<>();
        if (!pageInRange(pageNum, count, result)) {
            return new ResultMessage<>(false, "请求的页码大于总页数", result);
        }

        List<User> users = userService.findLikeUsernamePagination(username, pageSize, pageNum);
        result.put("users", toUserWithoutPrivateInfo(users));
        return new ResultMessage<>(true, "搜索成功", result);
    }

    private boolean pageInRange(int pageNum, int count, Map<String, Object> result) {
        int totalPages = (int) Math.ceil((double) count / (double) pageSize);
        result.put("totalPages", totalPages);
        return pageNum <= totalPages;
    }

    private List<UserWithoutPrivateInfo> toUserWithoutPrivateInfo(List<User> original) {
        return original
                .parallelStream()
                .map(user -> {
                    String role = IdToRoleName.get(user.getRole());
                    UserWithoutPrivateInfo userInfo = new UserWithoutPrivateInfo(user.getId(), user.getUsername(), role);
                    userInfo.setRealName(user.getRealName());
                    return userInfo;
                })
                .collect(Collectors.toList());
    }

    /**
     * 获取所有可以进行外业任务的人员，即外业人员和管理员
     */
    @RequestMapping("/all_worker")
    @ResponseBody
    public ResultMessage getAllWorkers() {
        List<Map<String, Object>> workers = userService
                .findAll()
                .parallelStream()
                .map(it -> new HashMap<String, Object>() {{
                    put("id", it.getId());
                    put("username", it.getRealName());
                }})
                .collect(Collectors.toList());
        return new ResultMessage<>(true, "成功获取外业人员列表", workers);
    }

    /**
     * 修改用户的角色
     *
     * @param id   用户 ID
     * @param role 修改之后的角色
     */
    @RequestMapping("/role_change")
    @ResponseBody
    public ResultMessage changeRole(@RequestParam("id")
                                            int id,
                                    @RequestParam("role")
                                    @NotBlank(message = "角色不能为空")
                                            String role) {
        // 根据角色名称查询角色 ID
        Optional<Role> optionalRole = roleService.findRoleByName(role);
        if (!optionalRole.isPresent()) {
            return new ResultMessage<>(false, "角色" + role + "不存在");
        }
        // 查询用户
        Optional<User> optionalUser = userService.findById(id);
        if (!optionalUser.isPresent()) {
            return new ResultMessage<>(false, "用户" + id + "不存在");
        }
        // 修改用户角色
        User user = optionalUser.get();
        Integer roleId = optionalRole.get().getId();
        user.setRole(roleId);
        user = userService.updateUser(user);
        if (user.getRole().equals(roleId)) {
            return new ResultMessage<>(true, "用户角色修改成功");
        } else {
            return new ResultMessage<>(false, "用户角色修改失败");
        }
    }

    @RequestMapping("/delete")
    @ResponseBody
    public ResultMessage deleteUserById(@RequestParam("id") int id) {
        if (userService.deleteById(id) == 1) {
            cacheService.delete(userCountCacheKey);
        }
        return new ResultMessage<>(true, "成功删除用户");
    }
    /**
     * 管理员重置指定用户密码为默认值 123456
     *
     * @param id 用户ID
     */
    @RequestMapping("/resetPassword")
    @ResponseBody
    public ResultMessage resetPasswordByAdmin(@RequestParam("id") int id) {
        // 查询用户
        Optional<User> optionalUser = userService.findById(id);
        if (!optionalUser.isPresent()) {
            return new ResultMessage<>(false, "用户不存在");
        }
        User user = optionalUser.get();

        // 固定密码 123456，加密后保存
        String frontEndMd5 = DigestUtils.md5DigestAsHex("123456".getBytes());
        frontEndMd5 = DigestUtils.md5DigestAsHex(frontEndMd5.getBytes());
        String hashed = PasswordEncoder.encode(frontEndMd5, user.getSalt());
        user.setPassword(hashed);
        // 更新用户信息
        user = userService.updateUser(user);

        if (!user.getPassword().equals(hashed)) {
            return new ResultMessage<>(false, "密码重置失败");
        }

        return new ResultMessage<>(true, "密码已重置为默认密码");
    }


    /**
     * 用户没有登录、没有权限时返回错误信息
     */
    @RequestMapping("/unauthc")
    @ResponseBody
    public ResultMessage unauthc() {
        return new ResultMessage<>(false, "用户未登录", 401);
    }

    @RequestMapping("/unautho")
    @ResponseBody
    public ResultMessage unautho() {
        return new ResultMessage<>(false, "用户没有权限进行此操作", 403);
    }
}
