package com.cf.imes.module.system.service.tokenconfig;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.cf.imes.framework.common.enums.CommonStatusEnum;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.Assert.AssertUtils;
import com.cf.imes.framework.common.util.date.DateUtils;
import com.cf.imes.framework.common.util.date.LocalDateTimeUtils;
import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.module.system.framework.jwt.config.JwtProperties;
import com.cf.imes.module.system.controller.admin.tokenconfig.vo.TokenConfigPageReqVO;
import com.cf.imes.module.system.controller.admin.tokenconfig.vo.TokenConfigRespVO;
import com.cf.imes.module.system.controller.admin.tokenconfig.vo.TokenConfigSaveReqVO;
import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO;
import com.cf.imes.module.system.dal.dataobject.tokenconfig.TokenConfigDO;
import com.cf.imes.module.system.dal.mysql.organ.OrganMapper;
import com.cf.imes.module.system.dal.mysql.tokenconfig.TokenConfigMapper;
import com.cf.imes.module.system.enums.token.TokenConfigAppTypeEnum;
import com.cf.imes.module.system.enums.token.TokenExpiresTimeTypeEnum;
import com.cf.imes.module.system.service.organ.OrganService;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import jakarta.annotation.Resource;
import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;

import static com.cf.imes.framework.security.core.util.SecurityFrameworkUtils.getUserOrganId;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.*;

/**
 * @author 配置管理接口 实现类
 */
@Slf4j
@Service
public class TokenConfigServiceImpl implements TokenConfigService{

    @Resource
    private TokenConfigMapper tokenConfigMapper;

    @Resource
    private OrganMapper organMapper;

    @Resource
    private OrganService organService;


    @Resource
    private JwtProperties jwtProperties;

    private static final String FIELD_IMES = "imes-";

    @Override
    public PageResult<TokenConfigRespVO> getTokenConfigPage(TokenConfigPageReqVO pageReqVO) {

        LocalDateTime[] localDateTimes = new LocalDateTime[1];
        if(pageReqVO.getTimeType() != null){
            LocalDate[] expiresTime = pageReqVO.getTimeType();
            localDateTimes = Arrays.stream(expiresTime)
                    .map(date -> date.atTime(LocalTime.MIDNIGHT))
                    .toArray(LocalDateTime[]::new);
            localDateTimes = LocalDateTimeUtils.generateTimeSection(localDateTimes);
        }

        PageResult<TokenConfigDO> pageResult = tokenConfigMapper.selectConfigPage(pageReqVO,localDateTimes);
        if(CollUtil.isEmpty(pageResult.getList())) {
            return new PageResult<>();
        }

        OrganizationDO organizationDO = organService.validOrgan(getUserOrganId());

        PageResult<TokenConfigRespVO> result = BeanUtils.toBean(pageResult, TokenConfigRespVO.class);

        result.getList().forEach(f->{
            f.setOrganName(organizationDO.getName());
            f.setAppToken(maskString(f.getAppToken()));

        });

        return result;


    }



    @Override
    public TokenConfigDO getTokenConConfig(Long id) {

        TokenConfigDO tokenConfigDO = validateTokenConfigExists(id);

        tokenConfigDO.setAppToken(maskString(tokenConfigDO.getAppToken()));

        return tokenConfigDO;

    }




    @Override
    public void insertTokenConfig(TokenConfigSaveReqVO reqVO) {

        Long organId = getUserOrganId();

        OrganizationDO organizationDO = organService.validOrgan(organId);

        List<Integer> appTypes = tokenConfigMapper.selectAppTypeByOrganId(organId);

        if(appTypes.contains(reqVO.getAppType())){
            throw new ServiceException(TOKEN_APP_TYPE_EXIST);
        }

        String token = generateBaseToken(organId, organizationDO.getContactMobile(), reqVO.getAppType(), jwtProperties.getSecret());

        LocalDateTime adjustedTime;

        if (reqVO.getCustomizeTime() != null){
            LocalDateTime localDateTime = LocalDate.parse(reqVO.getCustomizeTime()).atStartOfDay();
            adjustedTime = localDateTime.withHour(23).withMinute(59).withSecond(59);
        }else {
            AssertUtils.notEmpty(reqVO.getTimeType(),TOKEN_EXPIRES_TIME_IS_NULL);
            adjustedTime = TokenExpiresTimeTypeEnum.adjustTime(reqVO.getTimeType(), LocalDateTime.now());
        }


        tokenConfigMapper.insert(TokenConfigDO.builder()
                .appName(TokenConfigAppTypeEnum.fromType(reqVO.getAppType()).getName())
                .appToken(token)
                .appType(reqVO.getAppType())
                .organId(organId)
                .timeType(reqVO.getTimeType())
                .expiresTime(adjustedTime)
                .remark(reqVO.getRemark())
                .build());


    }



    @Override
    public void updateTokenConfig(TokenConfigSaveReqVO reqVO) {

        TokenConfigDO tokenConfigDO = validateTokenConfigExists(reqVO.getId());

        if(!tokenConfigDO.getAppType().equals(reqVO.getAppType())){
            throw new ServiceException(TOKEN_APP_TYPE_NO_UPDATE);
        }

        LocalDateTime adjustedTime = null;

        if (reqVO.getCustomizeTime() != null){
            LocalDateTime localDateTime = LocalDate.parse(reqVO.getCustomizeTime()).atStartOfDay();
            adjustedTime = localDateTime.withHour(23).withMinute(59).withSecond(59);
        }else if(reqVO.getTimeType() != null){
            adjustedTime = TokenExpiresTimeTypeEnum.adjustTime(reqVO.getTimeType(), tokenConfigDO.getCreateTime());
        }

        if(adjustedTime != null){

            if(DateUtils.isExpired(adjustedTime)){
                throw new ServiceException(TOKEN_EXPIRES_TIME_DATA_ERROR);
            }

            tokenConfigDO.setExpiresTime(adjustedTime);
        }

        tokenConfigDO.setRemark(reqVO.getRemark() == null ? tokenConfigDO.getRemark() : reqVO.getRemark());

        tokenConfigDO.setTimeType(reqVO.getTimeType());

        tokenConfigMapper.updateById(tokenConfigDO);


    }



    @Override
    public void deleteTokenConfig(Long id) {

        TokenConfigDO tokenConfigDO = tokenConfigMapper.selectById(id);

        if(ObjectUtil.isNull(tokenConfigDO)){
            throw new ServiceException(TOKEN_DATA_IS_NULL);
        }

        tokenConfigMapper.deleteTokenConfig(id);

    }




    @Override
    public Long checkTokenConfig(String token) {
        return getOrganIdByAppToken(token.substring(FIELD_IMES.length()));
    }

    @Override
    public Long getOrganIdByAppToken(String appToken) {
        // 应用 Token 必须存在且处于有效期内，避免失效配置继续访问外部接口。
        TokenConfigDO tokenConfigDO = tokenConfigMapper.selectByToken(appToken);

        if(tokenConfigDO == null){
            throw new ServiceException(TOKEN_CONFIG_NOT_EXISTS);
        }

        if (DateUtils.isExpired(tokenConfigDO.getExpiresTime())) {
            throw new ServiceException(TOKEN_TIME_IS_EXPIRES);
        }

        Long organId = tokenConfigDO.getOrganId();
        // 同步校验组织状态和有效期，保证返回的组织编号当前可用。
        OrganizationDO organizationDO = organMapper.selectById(organId);

        if (organizationDO == null) {
            throw new ServiceException(ORGAN_NOT_EXISTS);
        }

        if (organizationDO.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) {
            throw new ServiceException(ORGAN_DISABLE, organizationDO.getName());
        }

        if (DateUtils.isExpired(organizationDO.getExpireTime())) {
            throw new ServiceException(ORGAN_EXPIRE, organizationDO.getName());
        }

        return organId;

    }

    @Override
    public String tokenConfigCopy(Long id) {

        TokenConfigDO tokenConfigDO = validateTokenConfigExists(id);

        return FIELD_IMES+tokenConfigDO.getAppToken();
    }


    private TokenConfigDO validateTokenConfigExists(Long id) {

        TokenConfigDO tokenConfigDO = tokenConfigMapper.selectById(id);

        if (ObjectUtil.isNull(tokenConfigDO)) {
            throw new ServiceException(TOKEN_CONFIG_NOT_EXISTS);
        }

        return tokenConfigDO;
    }





    // token生成
    public static String generateToken(Map<String, Object> claims, String base64SecretKey) {
        SecretKey secretKey = Keys.hmacShaKeyFor(base64SecretKey.getBytes(StandardCharsets.UTF_8));

        return Jwts.builder()
                .addClaims(claims)
                .signWith(secretKey, SignatureAlgorithm.HS256)
                .compact();
    }




    // 生成token的信息
    public static String generateBaseToken(Long organId, String mobile,Integer appType, String base64SecretKey) {
        Map<String, Object> claims = new HashMap<>();
        claims.put("organId", organId);
        claims.put("mobile", mobile);
        claims.put("appType",appType);
        claims.put("nowTime",System.currentTimeMillis());
        String token = generateToken(claims, base64SecretKey);

        try {

            // 使用 SHA-256 处理 生成的 token
            MessageDigest shaDigest = MessageDigest.getInstance("SHA-256");
            byte[] hash = shaDigest.digest(token.getBytes());

            token = Base64.getEncoder().encodeToString(hash).toLowerCase();

            // 使用 MD5 对 token 进行二次处理
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] md5Digest = md.digest(token.getBytes());

            StringBuilder hexString = new StringBuilder();
            for (byte b : md5Digest) {
                String hex = Integer.toHexString(0xff & b);
                if (hex.length() == 1) {
                    hexString.append('0'); // 确保转换后的字符串是两位
                }
                hexString.append(hex);
            }
            token = hexString.toString();

        }catch (NoSuchAlgorithmException e){

            log.error("token二次处理失败"+e.getMessage());

            throw new ServiceException(INSERT_TOKEN_DATA_ERROR);

        }

        return token;
    }



    public static String maskString(String input) {
        int length = input.length();
        return FIELD_IMES+input.substring(0, 8) + "*".repeat(length - 8) + input.substring(length - 8);
    }



}
