
package com.bizzan.bitrade.controller.system;

import com.bizzan.bitrade.annotation.AccessLog;
import com.bizzan.bitrade.constant.AdminModule;
import com.bizzan.bitrade.constant.PageModel;
import com.bizzan.bitrade.constant.SysConstant;
import com.bizzan.bitrade.controller.common.BaseAdminController;
import com.bizzan.bitrade.core.Menu;
import com.bizzan.bitrade.entity.Admin;
import com.bizzan.bitrade.entity.Department;
import com.bizzan.bitrade.entity.QAdmin;
import com.bizzan.bitrade.entity.SysRole;
import com.bizzan.bitrade.service.*;
import com.bizzan.bitrade.util.BindingResultUtil;
import com.bizzan.bitrade.util.CaptchaUtil;
import com.bizzan.bitrade.util.GoogleAuthenticatorUtil;
import com.bizzan.bitrade.util.MessageResult;
import com.bizzan.bitrade.vendor.provider.SMSProvider;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.sparkframework.lang.Convert;
import com.sparkframework.security.Encrypt;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.util.Assert;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;

import com.bizzan.bitrade.util.RedisManager;

import javax.annotation.Resource;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;


/**
 * @author
 * @date 2020年12月19日
 */


@Slf4j
@Controller
@RequestMapping("/system/employee")
public class EmployeeController extends BaseAdminController {

    @Value("${spark.system.md5.key}")
    private String md5Key;

    @Autowired
    private SysRoleService sysRoleService;

    @Autowired
    private AdminService adminService;

    @Autowired
    private DepartmentService departmentService;
    @Resource
    private SysPermissionService sysPermissionService;

    @Autowired
    private RedisManager redisManager;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired(required = false)
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private SMSProvider smsProvider;
    @Autowired
    private JavaMailSender javaMailSender;

    @Value("${spring.mail.username}")
    private String from;
    @Value("${spark.system.host}")
    private String host;
    @Value("${spark.system.name}")
    private String company;

    @Value("${spark.system.admins}")
    private String admins;


    /**
     * 提交登录信息（第二步：captcha 校验通过后凭 tempToken 完成登录，返回 Redis Token）
     *
     * @param tempToken /check 接口返回的临时 token
     * @param request   HTTP 请求
     * @return 登录结果，包含 token、权限菜单、admin 信息
     */
    @RequestMapping(value = "sign/in")
    @ResponseBody
    @AccessLog(module = AdminModule.SYSTEM, operation = "提交登录信息Admin")
    public MessageResult doLogin(@RequestParam String tempToken, HttpServletRequest request) {
        // 1. 从 Redis 读取登录中间态（由 /check 接口写入，有效期 5 分钟）
        Admin admin = redisManager.getAdminLoginTemp(tempToken);
        if (admin == null) {
            return error("登录会话已过期，请重新验证");
        }

        // 2. 生成认证 Token，支持同一账号多设备同时登录（每次登录独立 Token）
        String authToken = UUID.randomUUID().toString().replace("-", "");
        redisManager.saveAdminToken(authToken, admin);

        // 3. 清理登录中间态
        redisManager.removeAdminLoginTemp(tempToken);

        // 4. 更新最后登录 IP 和时间
        adminService.update(new Date(), getRemoteIp(request), admin.getId());

        // 5. 获取当前用户的菜单权限
        List<Menu> list;
        if ("root".equalsIgnoreCase(admin.getUsername())) {
            list = sysRoleService.toMenus(sysPermissionService.findAll(), 0L);
        } else {
            list = sysRoleService.toMenus(sysRoleService.getPermissions(admin.getRoleId()), 0L);
        }

        // 6. 发送登录通知邮件
        String[] adminList = admins.split(",");
        for (String email : adminList) {
            sendEmailMsg(email, "管理员(UserName: " + admin.getUsername() + ") 登录后台", "管理员登录通知");
        }

        Map<String, Object> map = new HashMap<>();
        map.put("token", authToken);
        map.put("permissions", list);
        map.put("admin", admin);
        return success("登录成功", map);
    }


    /**
     * 发送邮件
     *
     * @param email
     * @param msg
     * @param subject
     * @throws MessagingException
     * @throws IOException
     * @throws TemplateException
     */
    @Async
    public void sendEmailMsg(String email, String msg, String subject) {
        try {
            MimeMessage mimeMessage = javaMailSender.createMimeMessage();
            MimeMessageHelper helper = null;
            helper = new MimeMessageHelper(mimeMessage, true);
            helper.setFrom(from);
            helper.setTo(email);
            helper.setSubject(company + "-" + subject);
            Map<String, Object> model = new HashMap<>(16);
            model.put("msg", msg);
            Configuration cfg = new Configuration(Configuration.VERSION_2_3_26);
            cfg.setClassForTemplateLoading(this.getClass(), "/templates");
            Template template = cfg.getTemplate("simpleMessage.ftl");
            String html = FreeMarkerTemplateUtils.processTemplateIntoString(template, model);
            helper.setText(html, true);

            //发送邮件
            javaMailSender.send(mimeMessage);
            log.info("send email for {},content:{}", email, html);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 登录第一步：校验用户名、密码和图形验证码
     * 验证通过后生成临时 Token（tempToken）存入 Redis，有效期 5 分钟。
     * 前端凭 tempToken 进入下一步（谷歌验证或直接登录）。
     * 注意：验证码从请求参数中读取，不依赖 Session。
     *
     * @param request HTTP 请求（从请求参数中读取 username、password、captcha）
     * @return 校验结果，包含 phone、googleState、tempToken
     */
    @RequestMapping(value = "/check")
    @ResponseBody
    @AccessLog(module = AdminModule.SYSTEM, operation = "判断后台登录输入手机验证码")
    public MessageResult valiatePhoneCode(HttpServletRequest request) {
        // 1. 从请求参数中读取登录信息（不使用 Session）
        String username = Convert.strToStr(request(request, "username"), "");
        String password = Convert.strToStr(request(request, "password"), "");
        String captcha = Convert.strToStr(request(request, "captcha"), "");

        if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
            return error("用户名或密码不能为空");
        }
        if (StringUtils.isBlank(captcha)) {
            return error("验证码不能为空");
        }

        // 2. 从 Redis 中校验图形验证码（key 由验证码生成接口写入）
        log.info("check接口-前端提交验证码：{}", captcha);
        boolean valid = false;
        if (stringRedisTemplate != null) {
            String redisKey = "captcha:" + captcha.toLowerCase();
            String redisVal = stringRedisTemplate.opsForValue().get(redisKey);
            log.info("check接口-Redis拿取到的值：{}", redisVal);
            if (redisVal != null) {
                stringRedisTemplate.delete(redisKey);
                valid = true;
            }
        }
        if (!valid) {
            return error("验证码不正确");
        }

        // 3. 校验用户名和密码
        log.info("md5Key {}", md5Key);
        password = Encrypt.MD5(password + md5Key);
        Admin admin = adminService.login(username, password);
        if (admin == null) {
            return error("用户名或密码不存在");
        }

        try {
            // 4. 生成临时 Token 写入 Redis，有效期 5 分钟
            String tempToken = UUID.randomUUID().toString().replace("-", "");
            redisManager.saveAdminLoginTemp(tempToken, admin);

            Map<String, Object> checkResult = new HashMap<>();
            checkResult.put("phone", admin.getMobilePhone());
            checkResult.put("googleState", admin.getGoogleState());
            checkResult.put("tempToken", tempToken);
            return success("", checkResult);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return error("登录校验失败");
    }


    /**
     * 获取谷歌验证信息（密钥 + 二维码 URL），用于已登录用户绑定谷歌验证前展示
     *
     * @param request HTTP 请求
     * @return 谷歌密钥和二维码 URL
     */
    @RequestMapping(value = "google/info", method = RequestMethod.GET)
    @ResponseBody
    public MessageResult googleInfo(HttpServletRequest request) {
        // 从已登录 admin（由 TokenInterceptor 注入 Session）获取用户名
        Admin sessionAdmin = getAdmin(request);
        if (sessionAdmin == null) {
            return error("请先登录");
        }
        String username = sessionAdmin.getUsername();
        Admin admin = adminService.findByUsername(username);
        if (admin == null) {
            return error("用户不存在");
        }

        String key = admin.getGoogleKey();
        if (StringUtils.isBlank(key)) {
            key = GoogleAuthenticatorUtil.generateSecretKey();
            admin.setGoogleKey(key);
            adminService.saveAdmin(admin);
        }
        String qrUrl = GoogleAuthenticatorUtil.getQRBarcodeURL(username, host, key);
        Map<String, Object> map = new HashMap<>();
        map.put("secret", key);
        map.put("qrUrl", qrUrl);
        return success(map);
    }

    /**
     * 绑定谷歌验证（已登录用户首次绑定）
     * 绑定成功后同步更新 Redis Token 中的 admin 信息。
     *
     * @param request    HTTP 请求
     * @param googleCode 谷歌验证码（从请求参数中读取）
     * @return 绑定结果
     */
    @RequestMapping(value = "google/bind", method = RequestMethod.POST)
    @ResponseBody
    public MessageResult googleBind(HttpServletRequest request, @RequestParam("googleCode") String googleCode) {
        Admin admin = getAdmin(request);
        if (admin == null) {
            return error("请先登录");
        }
        Admin freshAdmin = adminService.findOne(admin.getId());
        if (StringUtils.isBlank(freshAdmin.getGoogleKey())) {
            return error("谷歌密钥未生成，请联系管理员");
        }
        if (StringUtils.isBlank(googleCode)) {
            return error("请输入谷歌验证码");
        }
        try {
            GoogleAuthenticatorUtil ga = new GoogleAuthenticatorUtil();
            boolean valid = ga.check_code(freshAdmin.getGoogleKey(), Long.parseLong(googleCode.trim()), System.currentTimeMillis());
            if (!valid) {
                return error("谷歌验证码错误");
            }
        } catch (NumberFormatException e) {
            return error("验证码格式不正确");
        }
        freshAdmin.setGoogleState(1);
        adminService.saveAdmin(freshAdmin);

        // 同步更新 Redis Token 中的 admin 信息，避免 googleState 缓存过期前不一致
        String currentToken = (String) request.getAttribute("ADMIN_TOKEN");
        if (StringUtils.isNotBlank(currentToken)) {
            redisManager.saveAdminToken(currentToken, freshAdmin);
        }
        return success("谷歌验证绑定成功");
    }

    /**
     * 谷歌二次验证登录（已绑定谷歌验证的用户）
     * 凭 tempToken（/check 接口返回）+ 谷歌验证码完成登录，返回 Redis Token。
     *
     * @param request    HTTP 请求
     * @param tempToken  /check 接口返回的临时 token
     * @param googleCode 谷歌验证码（从请求参数中读取）
     * @return 登录结果，包含 token、权限菜单、admin 信息
     */
    @RequestMapping(value = "sign/in/google")
    @ResponseBody
    @AccessLog(module = AdminModule.SYSTEM, operation = "谷歌二次验证登录")
    public MessageResult signInGoogle(HttpServletRequest request,
                                      @RequestParam String tempToken,
                                      @RequestParam("googleCode") String googleCode) {
        // 1. 从 Redis 读取登录中间态
        Admin admin = redisManager.getAdminLoginTemp(tempToken);
        if (admin == null) {
            return error("登录会话已过期，请重新验证");
        }
        if (StringUtils.isBlank(googleCode)) {
            return error("请输入谷歌验证码");
        }

        // 2. 查询最新 admin 信息（确保 googleKey 最新）
        Admin freshAdmin = adminService.findOne(admin.getId());
        if (freshAdmin == null) {
            return error("用户不存在");
        }
        if (StringUtils.isBlank(freshAdmin.getGoogleKey())) {
            return error("谷歌密钥未绑定");
        }

        // 3. 校验谷歌验证码（从请求参数中读取，不从请求头读取）
        try {
            GoogleAuthenticatorUtil ga = new GoogleAuthenticatorUtil();
            boolean valid = ga.check_code(freshAdmin.getGoogleKey(), Long.parseLong(googleCode.trim()), System.currentTimeMillis());
            if (!valid) {
                return error("谷歌验证码错误");
            }
        } catch (NumberFormatException e) {
            return error("验证码格式不正确");
        }

        // 4. 生成登录 Token，写入 Redis（支持多设备同时登录）
        String authToken = UUID.randomUUID().toString().replace("-", "");
        redisManager.saveAdminToken(authToken, freshAdmin);
        redisManager.removeAdminLoginTemp(tempToken);
        adminService.update(new Date(), getRemoteIp(request), freshAdmin.getId());

        // 5. 获取菜单权限
        List<Menu> list;
        if ("root".equalsIgnoreCase(freshAdmin.getUsername())) {
            list = sysRoleService.toMenus(sysPermissionService.findAll(), 0L);
        } else {
            list = sysRoleService.toMenus(sysRoleService.getPermissions(freshAdmin.getRoleId()), 0L);
        }

        Map<String, Object> map = new HashMap<>();
        map.put("token", authToken);
        map.put("permissions", list);
        map.put("admin", freshAdmin);
        return success("登录成功", map);
    }

    /**
     * 退出登录：删除 Redis Token，清除 Shiro 认证状态
     *
     * @param request HTTP 请求（从请求头 Authorization 中读取 Token）
     * @return 退出结果
     */
    @RequestMapping(value = "logout")
    @ResponseBody
    @AccessLog(module = AdminModule.SYSTEM, operation = "退出登录")
    public MessageResult logout(HttpServletRequest request) {
        // 1. 从请求头读取 Token 并从 Redis 删除（使 token 立即失效）
        String token = request.getHeader("Authorization");
        if (StringUtils.isNotBlank(token)) {
            redisManager.removeAdminToken(token);
        }
        // 2. 清除 Shiro 认证状态
        SecurityUtils.getSubject().logout();
        return success();
    }

    /**
     * 管理员操作密码校验（用于资产划转等敏感操作前的二次验证）
     *
     * @param request
     * @param password 登录密码
     * @return
     */
    @PostMapping("verify-password")
    @ResponseBody
    @AccessLog(module = AdminModule.SYSTEM, operation = "管理员密码校验")
    public MessageResult verifyPassword(HttpServletRequest request, @RequestParam("password") String password) {
        if (StringUtils.isBlank(password)) {
            return error("密码不能为空");
        }
        Admin admin = getAdmin(request);
        if (admin == null) {
            return error("请先登录");
        }
        String encryptedPassword = Encrypt.MD5(password + md5Key);
        if (!encryptedPassword.equals(admin.getPassword())) {
            return error("密码错误");
        }
        return success("验证通过");
    }

    /**
     * 创建或更改后台用户
     *
     * @param admin
     * @param bindingResult
     * @return
     */


    @RequiresPermissions("system:employee:merge")
    @RequestMapping(value = "/merge")
    @ResponseBody
    @AccessLog(module = AdminModule.SYSTEM, operation = "创建或更改后台用户")
    @Transactional(rollbackFor = Exception.class)
    public MessageResult addAdmin(Admin admin,
                                  @RequestParam("departmentId") Long departmentId,
                                  BindingResult bindingResult) {
        MessageResult result = BindingResultUtil.validate(bindingResult);
        if (result != null) {
            return result;
        }
        Assert.notNull(departmentId, "请选择部门");
        Department department = departmentService.findOne(departmentId);
        admin.setDepartment(department);
        String password;
        if (admin.getId() != null) {
            Admin admin1 = adminService.findOne(admin.getId());
            admin.setLastLoginIp(admin1.getLastLoginIp());
            admin.setLastLoginTime(admin1.getLastLoginTime());
            //如果密码不为null更改密码
            if (StringUtils.isNotBlank(admin.getPassword())) {
                password = Encrypt.MD5(admin.getPassword() + md5Key);
            } else {
                password = admin1.getPassword();
            }
        } else {
            //这里是新增
            Admin a = adminService.findByUsername(admin.getUsername());
            if (a != null) {
                return error("用户名已存在！");
            }
            if (StringUtils.isBlank(admin.getPassword())) {
                return error("密码不能为空");
            }
            password = Encrypt.MD5(admin.getPassword() + md5Key);
            // 新账号自动生成谷歌验证密钥
            admin.setGoogleKey(GoogleAuthenticatorUtil.generateSecretKey());
            admin.setGoogleState(0);
        }
        admin.setPassword(password);
        adminService.saveAdmin(admin);
        return success("操作成功");
    }

    @ResponseBody
    @RequiresPermissions("system:employee:page-query")
    @PostMapping("page-query")
    @AccessLog(module = AdminModule.SYSTEM, operation = "分页查找后台用户admin")
    public MessageResult findAllAdminUser(
            PageModel pageModel,
            @RequestParam(value = "searchKey", defaultValue = "") String searchKey) {
        BooleanExpression predicate = QAdmin.admin.username.ne("root");
        if (StringUtils.isNotBlank(searchKey)) {
            predicate.and(QAdmin.admin.email.like(searchKey)
                    .or(QAdmin.admin.realName.like(searchKey))
                    .or(QAdmin.admin.mobilePhone.like(searchKey))
                    .or(QAdmin.admin.username.like(searchKey)));
        }
        Page<Admin> all = adminService.findAll(predicate, pageModel.getPageable());
        for (Admin admin : all.getContent()) {
            SysRole role = sysRoleService.findOne(admin.getRoleId());
            admin.setRoleName(role.getRole());
        }
        return success(all);
    }

    @RequiresPermissions("system:employee:update-password")
    @PostMapping("update-password")
    @ResponseBody
    public MessageResult updatePassword(Long id, String lastPassword, String newPassword) {
        Assert.notNull(id, "admin id 不能为null");
        Assert.notNull(lastPassword, "请输入原密码");
        Assert.notNull(newPassword, "请输入新密码");
        Admin admin = adminService.findOne(id);
        lastPassword = Encrypt.MD5(lastPassword + md5Key);
        Assert.isTrue(lastPassword.equalsIgnoreCase(admin.getPassword()), "密码错误");
        admin.setPassword(Encrypt.MD5(newPassword + md5Key));
        adminService.save(admin);
        return MessageResult.success("修改密码成功");
    }


    @RequiresPermissions("system:employee:update-password")
    @PostMapping("reset-password")
    @ResponseBody
    public MessageResult resetPassword(Long id) {
        Assert.notNull(id, "admin id 不能为null");
        Admin admin = adminService.findOne(id);
        admin.setPassword(Encrypt.MD5("123456" + md5Key));
        adminService.save(admin);
        return MessageResult.success("重置密码成功，默认密码123456");
    }


    /**
     * admin信息
     *
     * @param id
     * @return
     */


    @RequiresPermissions("system:employee:detail")
    @RequestMapping(value = "/detail")
    @ResponseBody
    @AccessLog(module = AdminModule.SYSTEM, operation = "后台用户Admin详情")
    public MessageResult adminDetail(Long id) {
        Map map = adminService.findAdminDetail(id);
        MessageResult result = success();
        result.setData(map);
        return result;
    }


    /**
     * admin信息
     *
     * @return
     */


    @RequiresPermissions("system:employee:deletes")
    @RequestMapping(value = "/deletes")
    @ResponseBody
    @AccessLog(module = AdminModule.SYSTEM, operation = "后台用户Admin详情")
    public MessageResult deletes(Long[] ids) {
        adminService.deletes(ids);
        return MessageResult.success("批量删除成功");
    }
}

