package com.yunzhupaas.controller;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.text.StrPool;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.Operation;
import com.yunzhupaas.base.service.SysconfigService;
import com.yunzhupaas.config.YunzhupaasOauthConfig;
import com.yunzhupaas.constant.MsgCode;
import com.yunzhupaas.consts.AuthConsts;
import com.yunzhupaas.consts.DeviceType;
import com.yunzhupaas.consts.LoginTicketStatus;
import com.yunzhupaas.consts.ScanCodeTicketStatus;
import com.yunzhupaas.database.util.TenantDataSourceUtil;
import com.yunzhupaas.granter.TokenGranter;
import com.yunzhupaas.granter.TokenGranterBuilder;
import com.yunzhupaas.granter.UserDetailsServiceBuilder;
import com.yunzhupaas.model.*;
import com.yunzhupaas.model.login.MeInfoVO;
import com.yunzhupaas.model.login.UserSystemVO;
import com.yunzhupaas.model.logout.LogoutResultModel;
import com.yunzhupaas.permission.controller.SocialsUserController;
import com.yunzhupaas.permission.model.socails.SocialsUserVo;
import com.yunzhupaas.permission.service.UserService;
import com.yunzhupaas.service.AuthService;
import com.yunzhupaas.base.ActionResult;
import com.yunzhupaas.service.LogService;
import com.yunzhupaas.util.NoDataSourceBind;
import com.yunzhupaas.base.UserInfo;
import com.yunzhupaas.config.ConfigValueUtil;
import com.yunzhupaas.model.login.PcUserVO;
import com.yunzhupaas.permission.entity.UserEntity;
import com.yunzhupaas.exception.LoginException;
import com.yunzhupaas.service.LoginService;
import com.yunzhupaas.util.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static com.yunzhupaas.consts.AuthConsts.PARAMS_YUNZHUPAAS_TICKET;

/**
 * 登录控制器
 *
 * @author 云筑产品开发平台组
 * @version V3.1.0
 * @copyright 深圳市乐程软件有限公司
 * @date 2024-09-26 上午9:18
 */
@Tag(name = "登陆数据", description = "oauth")
@Slf4j
@RestController
@RequestMapping("/api/oauth")
public class LoginController {

    @Autowired
    private UserService userApi;
    @Autowired
    private LoginService loginService;
    @Autowired
    private AuthService authService;
    @Autowired
    private ConfigValueUtil configValueUtil;
    @Autowired
    private YunzhupaasOauthConfig oauthConfig;
    @Autowired
    private SysconfigService sysConfigApi;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private TokenGranterBuilder tokenGranterBuilder;
    @Autowired
    private SocialsUserController socialsUserApi;
    @Autowired
    private UserDetailsServiceBuilder userDetailsServiceBuilder;
    @Autowired
    private LogService logApi;

    /**
     * 登陆
     *
     * @param parameters 登录参数
     * @return
     * @throws LoginException 登录异常
     */
    @NoDataSourceBind
    @Operation(summary = "登陆")
    @Parameters({
            @Parameter(name = "parameters", description = "登录参数", required = true)
    })
    @RequestMapping(value = "/Login/**", method = { RequestMethod.GET, RequestMethod.POST })
    public ActionResult<LoginVO> login(@RequestParam Map<String, String> parameters) throws LoginException {
        return authService.login(parameters);
    }

    /**
     * 验证密码
     *
     * @param loginForm 登录模型
     * @return
     * @throws LoginException 登录异常
     */
    @Operation(summary = "锁屏解锁登录")
    @Parameters({
            @Parameter(name = "loginForm", description = "登录模型", required = true)
    })
    @PostMapping("/LockScreen")
    public ActionResult lockScreen(@RequestBody LoginForm loginForm) throws LoginException {
        UserEntity userEntity = userApi.getUserByAccount(loginForm.getAccount());
        if (userEntity == null) {
            UserInfo userInfo = UserProvider.getUser();
            if (userInfo.getUserId() != null) {
                UserProvider.logoutByUserId(userInfo.getUserId());
            }
            throw new LoginException(MsgCode.FA001.get());
        }
        if (!Md5Util.getStringMd5(loginForm.getPassword().toLowerCase() + userEntity.getSecretkey().toLowerCase())
                .equals(userEntity.getPassword())) {
            throw new LoginException(MsgCode.OA020.get());
        }
        return ActionResult.success(MsgCode.SU005.get());
    }

    /**
     * 登录注销
     *
     * @param grandtype 登录类型
     * @return
     */
    @NoDataSourceBind
    @Operation(summary = "退出")
    @Parameters({
            @Parameter(name = "grandtype", description = "登录类型", required = true)
    })
    @RequestMapping(value = { "/Logout", "/Logout/{grandtype}" }, method = { RequestMethod.GET, RequestMethod.POST })
    public ActionResult<LogoutResultModel> logout(
            @PathVariable(value = "grandtype", required = false) String grandtype) {
        long millis = System.currentTimeMillis();
        TokenGranter tokenGranter = tokenGranterBuilder.getGranterByLogin(grandtype);
        if (tokenGranter != null) {
            UserInfo userInfo = UserProvider.getUser();
            logApi.writeLogAsync(userInfo.getUserId(), userInfo.getUserName() + "/" + userInfo.getUserAccount(), "退出登录",
                    userInfo, 1, 1, (System.currentTimeMillis() - millis));
            return tokenGranter.logout();
        }
        return ActionResult.success();
    }

    /**
     * 踢出指定用户, 推送Websocket用户被强制下线
     *
     * @param tokens   token集合
     * @param userId   租户id
     * @param tenantId 租户id
     */
    @NoDataSourceBind
    @Operation(summary = "踢出指定用户")
    @Parameters({
            @Parameter(name = "tokens", description = "token集合"),
            @Parameter(name = "userId", description = "租户id"),
            @Parameter(name = "tenantId", description = "租户id"),
    })
    @PostMapping(value = { "/KickoutToken" })
    public void kickoutByToken(@RequestParam(value = "tokens", required = false) String[] tokens,
            @RequestParam(name = "userId", required = false) String userId,
            @RequestParam(name = "tenantId", required = false) String tenantId) {
        if (StringUtil.isNotEmpty(tokens)) {
            authService.kickoutByToken(tokens);
        } else {
            authService.kickoutByUserId(userId, tenantId);
        }
    }

    /**
     * 获取用户登录信息
     *
     * @param type Web/App
     * @return
     * @throws LoginException 登录异常
     */
    @Operation(summary = "获取用户登录信息")
    @Parameters({
            @Parameter(name = "type", description = "Web/App")
    })
    @GetMapping("/CurrentUser")
    public ActionResult<PcUserVO> currentUser(String type, String systemCode) throws LoginException {
        if (StringUtil.isEmpty(type)) {
            type = "Web";
        } else {
            type = "App";
        }
        UserInfo userInfo = UserProvider.getUser();
        if (DeviceType.TEMPUSERLIMITED.getDevice().equals(userInfo.getLoginDevice())) {
            throw new LoginException(MsgCode.OA022.get());
        }
        PcUserVO pcUserVO = loginService.getCurrentUser(type, systemCode);
        if (pcUserVO == null) {
            throw new LoginException(MsgCode.LOG001.get());
        }
        if (pcUserVO.getMenuList().isEmpty() && StringUtil.isEmpty(systemCode)) {
            List<UserSystemVO> standingList = pcUserVO.getUserInfo().getStandingList();
            for (int i = standingList.size() - 1; i >= 0; i--) {
                UserSystemVO systemVO = standingList.get(i);
                if (!systemVO.isCurrentStanding()) {
                    UserEntity user = new UserEntity();
                    user.setId(userInfo.getUserId());
                    if ("Web".equals(type)) {
                        user.setStanding(Integer.parseInt(systemVO.getId()));
                    } else {
                        user.setAppStanding(Integer.parseInt(systemVO.getId()));
                    }
                    boolean update = userApi.updateById(user);
                    if (update) {
                        pcUserVO = loginService.getCurrentUser(type, systemCode);
                    }
                    if (!pcUserVO.getMenuList().isEmpty())
                        break;
                }
            }
        }
        if (pcUserVO.getMenuList().isEmpty()) {
            UserProvider.logout();
        }
        return ActionResult.success(pcUserVO);
    }

    /**
     * 修改密码信息发送
     *
     */
    @Operation(summary = "修改密码信息发送")
    @PostMapping("/updatePasswordMessage")
    public ActionResult updatePasswordMessage() {
        loginService.updatePasswordMessage();
        return ActionResult.success();
    }

    /**
     * 图形验证码
     *
     * @param codeLength 验证码长度
     * @param timestamp  验证码标识
     */
    @NoDataSourceBind()
    @Operation(summary = "图形验证码")
    @Parameters({
            @Parameter(name = "codeLength", description = "验证码长度", required = true),
            @Parameter(name = "timestamp", description = "验证码标识", required = true)
    })
    @GetMapping("/ImageCode/{codeLength}/{timestamp}")
    public void imageCode(@PathVariable("codeLength") Integer codeLength, @PathVariable("timestamp") String timestamp) {
        DownUtil.downCode(codeLength);
        redisUtil.insert(timestamp, ServletUtil.getSession().getAttribute(CodeUtil.RANDOMCODEKEY), 300);
    }

    /**
     * 注销用户
     *
     * @return
     */
    @Operation(summary = "注销用户")
    @PostMapping("/logoutCurrentUser")
    public ActionResult logoutCurrentUser() {
        UserInfo userInfo = UserProvider.getUser();
        if (userInfo.getIsAdministrator() != null && UserProvider.getUser().getIsAdministrator()) {
            return ActionResult.fail(MsgCode.OA023.get());
        }
        if (userInfo.getIsAdministrator() != null) {
            if (!userInfo.getIsAdministrator()) {
                userApi.delete(userApi.getInfo(userInfo.getUserId()));
                UserProvider.kickoutByUserId(userInfo.getUserId(), userInfo.getTenantId());
            }
        }
        return ActionResult.success(MsgCode.SU005.get());
    }

    /**
     * 判断是否需要验证码
     *
     * @param account 账号
     */
    @NoDataSourceBind()
    @Operation(summary = "判断是否需要验证码")
    @Parameters({
            @Parameter(name = "account", description = "账号", required = true)
    })
    @GetMapping("/getConfig/{account}")
    public ActionResult<LoginModel> check(@PathVariable("account") String account) throws LoginException {
        LoginModel loginModel = new LoginModel();
        if (configValueUtil.isMultiTenancy()) {
            LoginForm loginForm = new LoginForm();
            loginForm.setAccount(account);
            UserInfo userInfo = new UserInfo();
            userInfo.setUserAccount(loginForm.getAccount());
            userInfo = loginService.getTenantAccount(userInfo);
        }
        // 获取配置
        BaseSystemInfo sysConfigInfo = sysConfigApi.getSysInfo();
        // 是否开启验证码
        if (Objects.nonNull(sysConfigInfo) && "1".equals(String.valueOf(sysConfigInfo.getEnableVerificationCode()))) {
            loginModel.setEnableVerificationCode(1);
            Integer verificationCodeNumber = sysConfigInfo.getVerificationCodeNumber();
            loginModel.setVerificationCodeNumber(verificationCodeNumber == null ? 4 : verificationCodeNumber);
            return ActionResult.success(loginModel);
        }
        loginModel.setEnableVerificationCode(0);
        return ActionResult.success(loginModel);
    }

    /**
     * 获取登录配置, 是否需要跳转、第三方登录信息
     *
     * @return {re}
     * @throws LoginException 登录异常
     */
    @NoDataSourceBind()
    @Operation(summary = "获取登录配置")
    @GetMapping("/getLoginConfig")
    public ActionResult<LoginConfigModel> getLoginConfig() {
        LoginConfigModel loginConfigModel = new LoginConfigModel();
        if (oauthConfig.getSsoEnabled()) {
            String url = oauthConfig.getLoginPath() + StrPool.SLASH + oauthConfig.getDefaultSSO();
            loginConfigModel.setRedirect(true);
            loginConfigModel.setUrl(url);
            loginConfigModel.setTicketParams(PARAMS_YUNZHUPAAS_TICKET);
        } else {
            // 追加第三方登录配置
            List<SocialsUserVo> loginList = socialsUserApi.getLoginList(PARAMS_YUNZHUPAAS_TICKET.toUpperCase());
            if (CollectionUtil.isNotEmpty(loginList)) {
                loginConfigModel.setSocialsList(loginList);
                loginConfigModel.setRedirect(false);
                loginConfigModel.setTicketParams(PARAMS_YUNZHUPAAS_TICKET);
            }
        }
        return ActionResult.success(loginConfigModel);
    }

    /**
     * 获取登录票据
     * 
     * @return {msg:有效期, data:票据}
     */
    @NoDataSourceBind()
    @Operation(summary = "获取登录票据")
    @GetMapping("/getTicket")
    public ActionResult<String> getTicket() {
        LoginTicketModel ticketModel = new LoginTicketModel();
        ticketModel.setTicketTimeout(System.currentTimeMillis() + oauthConfig.getTicketTimeout() * 1000);
        String ticket = TicketUtil.createTicket(ticketModel, oauthConfig.getTicketTimeout());
        return ActionResult.success(ticketModel.getTicketTimeout().toString(), ticket);
    }

    /**
     * 检测票据登录状态
     * 
     * @return {re}
     * @throws LoginException
     */
    @NoDataSourceBind()
    @Operation(summary = "获取登录状态")
    @GetMapping("/getTicketStatus/{ticket}")
    public ActionResult<LoginTicketModel> getTicketStatus(@PathVariable("ticket") String ticket) {
        LoginTicketModel ticketModel = TicketUtil.parseTicket(ticket);
        if (ticketModel == null) {
            ticketModel = new LoginTicketModel().setStatus(LoginTicketStatus.Invalid.getStatus()).setValue("票据失效！");
        } else {
            if (ticketModel.getStatus() != LoginTicketStatus.UnLogin.getStatus()
                    && ticketModel.getStatus() != LoginTicketStatus.UnBind.getStatus()) {
                TicketUtil.deleteTicket(ticket);
            }
        }
        return ActionResult.success(ticketModel);
    }

    /**
     * 官网重置密码专用
     *
     * @return
     */
    @NoDataSourceBind()
    @Operation(summary = "官网重置密码专用")
    @GetMapping("/resetOfficialPassword/{mobile}/{code}")
    public ActionResult resetOfficialPassword(@PathVariable("mobile") String mobile, @PathVariable("code") String code)
            throws LoginException {
        // 校验验证码
        TenantDataSourceUtil.checkOfficialSmsCode(mobile, code, 2);
        // 切换租户
        LoginForm loginForm = new LoginForm();
        loginForm.setAccount(mobile);
        UserInfo userInfo = new UserInfo();
        userInfo.setUserAccount(loginForm.getAccount());
        try {
            userInfo = loginService.getTenantAccount(userInfo);
        } catch (Exception e) {
            return ActionResult.fail(MsgCode.LOG105.get());
        }

        // 重置密码 123456
        UserEntity userEntity = userDetailsServiceBuilder.getUserDetailService(AuthConsts.USERDETAIL_ACCOUNT)
                .loadUserEntity(userInfo);
        userEntity.setPassword("e10adc3949ba59abbe56e057f20f883e");
        userEntity.setPassword(
                Md5Util.getStringMd5(userEntity.getPassword().toLowerCase() + userEntity.getSecretkey().toLowerCase()));
        boolean result = userApi.updateById(userEntity);
        if (result) {
            return ActionResult.success(MsgCode.LOG205.get());
        } else {
            return ActionResult.fail(MsgCode.LOG206.get());
        }
    }

    /**
     * 生成扫码凭证
     *
     * @return
     */
    @NoDataSourceBind()
    @Operation(summary = "生成扫码凭证")
    @GetMapping("/codeCertificate")
    public ActionResult codeCertificate() throws LoginException {
        ScanCodeLoginConfigModel ticketModel = new ScanCodeLoginConfigModel();
        ticketModel.setTicketTimeout(System.currentTimeMillis() + configValueUtil.getCodeCertificateTimeout() * 1000);
        String ticket = TicketUtil.createTicket(ticketModel, configValueUtil.getCodeCertificateTimeout());
        return ActionResult.success(ticketModel.getTicketTimeout().toString(), ticket);
    }

    /**
     * 获取扫码凭证状态
     *
     * @param ticket
     * @return
     * @throws LoginException
     */
    @NoDataSourceBind()
    @Operation(summary = "获取扫码凭证状态")
    @GetMapping("/codeCertificateStatus/{ticket}")
    public ActionResult codeCertificateStatus(@PathVariable("ticket") String ticket) throws LoginException {
        ScanCodeLoginConfigModel ticketModel = TicketUtil.parseTicket(ticket);
        if (ticketModel == null) {
            ticketModel = new ScanCodeLoginConfigModel();
            ticketModel.setStatus(ScanCodeTicketStatus.Invalid.getStatus());
        }
        return ActionResult.success(ticketModel);
    }

    /**
     * 确认登录
     *
     * @param ticket
     * @return
     * @throws LoginException
     */
    @NoDataSourceBind()
    @Operation(summary = "确认登录")
    @GetMapping("/confirmLogin/{ticket}")
    public ActionResult confirmLogin(@PathVariable("ticket") String ticket) throws LoginException {
        ScanCodeLoginConfigModel ticketModel = TicketUtil.parseTicket(ticket);
        if (ticketModel == null || !Objects.equals(ticketModel.getStatus(), 1)) {
            ticketModel = new ScanCodeLoginConfigModel();
            ticketModel.setStatus(ScanCodeTicketStatus.Invalid.getStatus());
            return ActionResult.success(ticketModel);
        }
        ticketModel.setStatus(ScanCodeTicketStatus.Success.getStatus());
        Map<String, String> parameters = new HashMap<>();
        parameters.put("grant_type", "scancode");
        parameters.put("token", UserProvider.getToken());
        ticketModel.setValue(authService.login(parameters).getData().getToken());
        TicketUtil.updateTicket(ticket, ticketModel, null);
        return ActionResult.success(ticketModel);
    }

    /**
     * 更改扫码凭证状态
     *
     * @param ticket
     * @return
     * @throws LoginException
     */
    @NoDataSourceBind()
    @Operation(summary = "更改扫码凭证状态")
    @GetMapping("/setCodeCertificateStatus/{ticket}/{status}")
    public ActionResult setCodeCertificateStatus(@PathVariable("ticket") String ticket,
            @PathVariable("status") Integer status) throws LoginException {
        ScanCodeLoginConfigModel ticketModel = TicketUtil.parseTicket(ticket);
        if (ticketModel == null) {
            ticketModel = new ScanCodeLoginConfigModel();
            ticketModel.setStatus(ScanCodeTicketStatus.Invalid.getStatus());
        } else {
            ticketModel.setStatus(status);
        }
        ticketModel.setTicketTimeout(System.currentTimeMillis() + configValueUtil.getCodeCertificateTimeout() * 1000);
        TicketUtil.updateTicket(ticket, ticketModel, configValueUtil.getCodeCertificateTimeout());
        return ActionResult.success(ticketModel);
    }

    /**
     * 获取用户登录信息
     *
     * @return
     * @throws LoginException 登录异常
     */
    @Operation(summary = "获取用户基本信息")
    @GetMapping("/me")
    public ActionResult<MeInfoVO> me() throws LoginException {
        UserInfo userInfo = UserProvider.getUser();
        MeInfoVO meInfoVO = new MeInfoVO()
                .setUserName(userInfo.getUserName())
                .setUserId(userInfo.getUserId())
                .setUserAccount(userInfo.getUserAccount())
                .setTenantId(userInfo.getTenantId());
        return ActionResult.success(meInfoVO);
    }

    public static void main(String[] args) {
        System.out.println(Md5Util.getStringMd5("123456" + "26916bdf390242c9b0ac7ec1442a329e".toLowerCase()));
    }
}
