package org.dromara.main.controller;

import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.constant.Constants;
import org.dromara.common.core.constant.GlobalConstants;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.domain.dto.PostDTO;
import org.dromara.common.core.domain.dto.RoleDTO;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.domain.model.SmsLoginBody;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.ValidatorUtils;
import org.dromara.common.ratelimiter.annotation.RateLimiter;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.web.core.BaseController;
import org.dromara.main.domain.bo.MainCompanyApplyBo;
import org.dromara.main.domain.vo.MainCompanyApplyVo;
import org.dromara.main.service.IMainCompanyApplyService;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.dromara.system.domain.SysUser;
import org.dromara.system.domain.vo.SysClientVo;
import org.dromara.system.domain.vo.SysDeptVo;
import org.dromara.system.domain.vo.SysPostVo;
import org.dromara.system.domain.vo.SysRoleVo;
import org.dromara.system.domain.vo.SysTenantVo;
import org.dromara.system.domain.vo.SysUserVo;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.system.service.ISysClientService;
import org.dromara.system.service.ISysDeptService;
import org.dromara.system.service.ISysPermissionService;
import org.dromara.system.service.ISysPostService;
import org.dromara.system.service.ISysRoleService;
import org.dromara.system.service.ISysTenantService;
import org.springframework.beans.factory.annotation.Value;
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.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.Duration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * 门户认证Controller（公开接口，无需登录）
 *
 * @author sj
 */
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/portal/auth")
public class PortalAuthController extends BaseController {

    private final IMainCompanyApplyService mainCompanyApplyService;
    private final ISysClientService clientService;
    private final ISysTenantService tenantService;
    private final ISysPermissionService permissionService;
    private final ISysRoleService roleService;
    private final ISysPostService postService;
    private final ISysDeptService deptService;
    private final SysUserMapper userMapper;

    @Value("${spring.profiles.active:}")
    private String activeProfile;

    @Value("${manage.jumpTo:}")
    private String manageJumpTo;

    /**
     * 发送短信验证码
     *
     * @param phonenumber 手机号
     */
    @SaIgnore
    @RateLimiter(key = "#phonenumber", time = 60, count = 1)
    @GetMapping("/sms/code")
    public R<Void> smsCode(@NotBlank(message = "手机号不能为空") String phonenumber) {
        String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
        String code = RandomUtil.randomNumbers(4);
        // 验证码缓存5分钟
        RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));

        if ("dev".equalsIgnoreCase(activeProfile)) {
            log.info("开发环境短信验证码: phone={}, code={}", phonenumber, code);
            return R.ok();
        }

        // 发送短信验证码
        // 验证码模板id 需要在短信服务商配置
        String templateId = "";
        LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
        map.put("code", code);

        try {
            SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
            SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map);
            if (!smsResponse.isSuccess()) {
                log.error("验证码短信发送异常 => {}", smsResponse);
                return R.fail(smsResponse.getData().toString());
            }
        } catch (Exception e) {
            log.error("验证码短信发送异常 => {}", e.getMessage());
            throw new ServiceException("短信发送失败，请稍后重试");
        }

        return R.ok();
    }

    /**
     * 门户短信登录
     */
    @SaIgnore
    @PostMapping("/smsLogin")
    public R<Map<String, Object>> smsLogin(@Validated @RequestBody SmsLoginBody loginBody) {
        ValidatorUtils.validate(loginBody);

        String clientId = loginBody.getClientId();
        String grantType = loginBody.getGrantType();
        if (!"sms".equalsIgnoreCase(grantType)) {
            return R.fail("授权类型错误");
        }

        SysClientVo client = clientService.queryByClientId(clientId);
        if (ObjectUtil.isNull(client) || !StringUtils.contains(client.getGrantType(), grantType)) {
            log.info("门户登录客户端异常 clientId={}, grantType={}", clientId, grantType);
            return R.fail("客户端配置不存在或未开启短信认证");
        }
        if (!SystemConstants.NORMAL.equals(client.getStatus())) {
            return R.fail("客户端已被停用");
        }

        MainCompanyApplyVo companyApply = queryLatestCompanyApply(loginBody.getPhonenumber());
        if (companyApply == null) {
            return R.fail("该手机号未注册企业账号");
        }

        Integer applyStatus = companyApply.getApplyStatus();
        Map<String, Object> result = new HashMap<>(4);

        if (!Integer.valueOf(2).equals(applyStatus)) {
            result.put("auditStatus", Integer.valueOf(3).equals(applyStatus) ? 2 : 0);
            result.put("company", companyApply);
            return R.ok(result);
        }

        try {
            SysUserVo employee = queryUserByPhone(loginBody.getPhonenumber());
            if (employee == null) {
                return R.fail("登录账号不存在，请联系管理员");
            }
            if (!SystemConstants.NORMAL.equals(employee.getStatus())) {
                return R.fail("登录账号已被停用");
            }

            validateSmsCode(loginBody.getPhonenumber(), loginBody.getSmsCode());
            LoginUser loginUser = buildLoginUser(employee, client);
            issueToken(loginUser, client);
            RedisUtils.deleteObject(GlobalConstants.CAPTCHA_CODE_KEY + loginBody.getPhonenumber());

            SysTenantVo company = null;
            if (StringUtils.isNotBlank(companyApply.getTenantId())) {
                company = tenantService.queryByTenantId(companyApply.getTenantId());
            }

            result.put("auditStatus", 1);
            result.put("token", StpUtil.getTokenValue());
            result.put("clientId", client.getClientId());
            result.put("employee", employee);
            result.put("company", ObjectUtil.defaultIfNull(company, companyApply));
            return R.ok(result);
        } catch (ServiceException e) {
            throw e;
        } catch (Exception e) {
            log.error("门户短信登录失败: {}", e.getMessage(), e);
            throw new ServiceException("登录失败，请稍后重试");
        }
    }

    /**
     * 获取当前登录信息
     */
    @GetMapping("/getInfo")
    public R<Map<String, Object>> getInfo() {
        Long userId = LoginHelper.getUserId();
        if (userId == null) {
            return R.fail("未登录或token已失效");
        }

        SysUserVo employee = userMapper.selectVoById(userId);
        if (employee == null) {
            return R.fail("用户信息不存在");
        }

        SysTenantVo company = null;
        if (StringUtils.isNotBlank(employee.getTenantId())) {
            company = tenantService.queryByTenantId(employee.getTenantId());
        }

        Map<String, Object> result = new HashMap<>(2);
        result.put("employee", employee);
        result.put("company", company);
        return R.ok(result);
    }

    @SaIgnore
    @GetMapping("/publicConfig")
    public R<Map<String, String>> getPublicConfig() {
        Map<String, String> result = new LinkedHashMap<>(1);
        result.put("manageJumpTo", manageJumpTo);
        return R.ok(result);
    }

    /**
     * 退出登录
     */
    @SaIgnore
    @PostMapping("/logout")
    public R<Void> logout() {
        try {
            if (LoginHelper.isLogin()) {
                StpUtil.logout();
            }
        } catch (Exception e) {
            log.warn("门户退出登录异常: {}", e.getMessage());
        }
        return R.ok();
    }

    private MainCompanyApplyVo queryLatestCompanyApply(String phonenumber) {
        MainCompanyApplyBo bo = new MainCompanyApplyBo();
        bo.setMobile(phonenumber);
        List<MainCompanyApplyVo> list = mainCompanyApplyService.queryList(bo);
        return list.isEmpty() ? null : list.get(0);
    }

    private SysUserVo queryUserByPhone(String phonenumber) {
        return userMapper.selectVoOne(new LambdaQueryWrapper<SysUser>()
            .eq(SysUser::getPhonenumber, phonenumber)
            .last("limit 1"));
    }

    private void validateSmsCode(String phonenumber, String smsCode) {
        String cacheKey = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
        String cachedCode = RedisUtils.getCacheObject(cacheKey);
        if (StringUtils.isBlank(cachedCode)) {
            throw new ServiceException("验证码已过期，请重新获取");
        }
        if (!StringUtils.equals(cachedCode, smsCode)) {
            throw new ServiceException("验证码错误");
        }
    }

    private LoginUser buildLoginUser(SysUserVo user, SysClientVo client) {
        LoginUser loginUser = new LoginUser();
        Long userId = user.getUserId();
        loginUser.setTenantId(user.getTenantId());
        loginUser.setUserId(userId);
        loginUser.setDeptId(user.getDeptId());
        loginUser.setUsername(user.getUserName());
        loginUser.setNickname(user.getNickName());
        loginUser.setUserType(user.getUserType());
        loginUser.setMenuPermission(permissionService.getMenuPermission(userId));
        loginUser.setRolePermission(permissionService.getRolePermission(userId));
        loginUser.setClientKey(client.getClientKey());
        loginUser.setDeviceType(client.getDeviceType());
        if (ObjectUtil.isNotNull(user.getDeptId())) {
            Opt<SysDeptVo> deptOpt = Opt.of(user.getDeptId()).map(deptService::selectDeptById);
            loginUser.setDeptName(deptOpt.map(SysDeptVo::getDeptName).orElse(StringUtils.EMPTY));
            loginUser.setDeptCategory(deptOpt.map(SysDeptVo::getDeptCategory).orElse(StringUtils.EMPTY));
        }
        List<SysRoleVo> roles = roleService.selectRolesByUserId(userId);
        List<SysPostVo> posts = postService.selectPostsByUserId(userId);
        loginUser.setRoles(BeanUtil.copyToList(roles, RoleDTO.class));
        loginUser.setPosts(BeanUtil.copyToList(posts, PostDTO.class));
        return loginUser;
    }

    private void issueToken(LoginUser loginUser, SysClientVo client) {
        SaLoginParameter model = new SaLoginParameter();
        model.setDeviceType(client.getDeviceType());
        model.setTimeout(client.getTimeout());
        model.setActiveTimeout(client.getActiveTimeout());
        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
        LoginHelper.login(loginUser, model);
    }
}
