package jnpf.franchisee.consumer;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import jnpf.base.UserInfo;
import jnpf.config.ConfigValueUtil;
import jnpf.database.util.TenantDataSourceUtil;
import jnpf.exception.LoginException;
import jnpf.franchisee.mapper.FranchiseeMapper;
import jnpf.message.enums.permission.v2.OperationTypeMessageEnums;
import jnpf.message.enums.permission.v2.OrganizeCategoryMessageEnums;
import jnpf.message.model.permission.v2.OrganizeUpdateMessageDTO;
import jnpf.model.franchisee.po.FranchiseeEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.stream.annotation.EnableBinding;
import org.springframework.cloud.stream.annotation.StreamListener;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * 加盟商门店数量写扩散消费者
 */
@Slf4j
@Component
@EnableBinding(FranchiseeConsumerSource.class)
public class FranchiseeStoreNumConsumer {

    private static final String IDEMPOTENT_KEY_PREFIX = "franchisee:storeNum:consume";
    private static final String IDEMPOTENT_VALUE = "1";
    private static final long IDEMPOTENT_EXPIRE_DAYS = 1L;

    @Resource
    private FranchiseeMapper franchiseeMapper;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    @Lazy
    private ConfigValueUtil configValueUtil;

    /**
     * 监听组织消息中的门店更新事件，执行加盟商门店数量写扩散。
     * <p>
     * 幂等与并发控制在上层消费入口处理：使用消息头 ROCKET_KEYS 作为 setnx 键，过期时间 1 天。
     * 如果缓存过期后发生重复消费导致数量偏差，会在“查询加盟商门店数量接口”按门店表实时修正。
     *
     * @param message 消息体
     */
    @StreamListener(target = FranchiseeConsumerSource.INPUT, condition = "headers['ROCKET_TAGS'] == 'TAG_ORGANIZE'")
    public void receiveStoreFranchiseeChange(Message<String> message) {
        String payload = message.getPayload();
        if (StrUtil.isBlank(payload)) {
            return;
        }

        MessageHeaders headers = message.getHeaders();
        String rocketKeys = headers.get("ROCKET_KEYS", String.class);
        if (StrUtil.isBlank(rocketKeys)) {
            log.warn("加盟商门店数量写扩散消息缺少ROCKET_KEYS，跳过处理。payload={}", payload);
            return;
        }

        String idempotentKey = IDEMPOTENT_KEY_PREFIX + ":" + rocketKeys;
        Boolean firstConsume = stringRedisTemplate.opsForValue()
                .setIfAbsent(idempotentKey, IDEMPOTENT_VALUE, IDEMPOTENT_EXPIRE_DAYS, TimeUnit.DAYS);
        if (!Boolean.TRUE.equals(firstConsume)) {
            return;
        }

        List<OrganizeUpdateMessageDTO> messageList;
        try {
            messageList = JSONUtil.toList(JSONUtil.parseArray(payload), OrganizeUpdateMessageDTO.class);
        } catch (Exception e) {
            log.error("加盟商门店数量写扩散消息解析失败，payload={}", payload, e);
            return;
        }
        if (CollUtil.isEmpty(messageList)) {
            return;
        }

        for (OrganizeUpdateMessageDTO item : messageList) {
            try {
                processStoreUpdate(item);
            } catch (Exception e) {
                log.error("处理加盟商门店数量写扩散消息失败，item={}", JSONUtil.toJsonStr(item), e);
            }
        }
    }

    /**
     * 处理单条门店更新消息
     *
     * @param item 消息项
     */
    private void processStoreUpdate(OrganizeUpdateMessageDTO item) {
        if (item == null) {
            return;
        }
        if (!OrganizeCategoryMessageEnums.STORE.equals(item.getOrganizeCategoryEnum())) {
            return;
        }
        log.error("处理加盟商门店数量写扩散消息，item={}", JSONUtil.toJsonStr(item));
        OperationTypeMessageEnums operationType = item.getOperationTypeEnum();
        if (operationType == null) {
            return;
        }

        String tenantId = StrUtil.trim(item.getTenantId());
        if (StrUtil.isBlank(tenantId)) {
            return;
        }
        String oldFranchiseeId = StrUtil.trim(item.getOldFranchiseeId());
        JSONObject jsonObject = parseJsonEntity(item.getJsonEntity());
        String newFranchiseeId = jsonObject.getStr("franchiseeId");
        Date lastModifyTime = jsonObject.getDate("lastModifyTime");//使用门店表修改时间，作为事件水位线。
        if(Objects.isNull(lastModifyTime)){
            log.error("processStoreUpdate lastModifyTime is null.item:{}",JSONUtil.toJsonStr(item));
            return;
        }
        long updateStoreNumTimestamp = lastModifyTime.getTime();
        if (OperationTypeMessageEnums.ADD.equals(operationType)) {
            if (StrUtil.isBlank(newFranchiseeId)) {
                return;
            }
            switchTenant(tenantId);
            updateStoreNum(newFranchiseeId, updateStoreNumTimestamp,1);
            return;
        }

        if (OperationTypeMessageEnums.DELETE.equals(operationType)) {
            String deleteFranchiseeId = StrUtil.isNotBlank(oldFranchiseeId) ? oldFranchiseeId : newFranchiseeId;
            if (StrUtil.isBlank(deleteFranchiseeId)) {
                return;
            }
            switchTenant(tenantId);
            updateStoreNum(deleteFranchiseeId, updateStoreNumTimestamp,-1);
            return;
        }

        if (OperationTypeMessageEnums.UPDATE.equals(operationType)) {
            if (StrUtil.equals(oldFranchiseeId, newFranchiseeId)) {
                return;
            }
            switchTenant(tenantId);
            if (StrUtil.isNotBlank(newFranchiseeId)) {
                updateStoreNum(newFranchiseeId, updateStoreNumTimestamp,1);
            }
            if (StrUtil.isNotBlank(oldFranchiseeId)) {
                updateStoreNum(oldFranchiseeId, updateStoreNumTimestamp,-1);
            }
        }
    }

    /**
     * 解析消息中的新加盟商ID
     *
     * @param jsonEntity 门店JSON实体
     * @return 新加盟商ID
     */
    private String parseNewFranchiseeId(String jsonEntity) {
        if (StrUtil.isBlank(jsonEntity) || !JSONUtil.isTypeJSONObject(jsonEntity)) {
            return null;
        }
        try {
            JSONObject jsonObject = JSONUtil.parseObj(jsonEntity);
            return StrUtil.trim(jsonObject.getStr("franchiseeId"));
        } catch (Exception e) {
            return null;
        }
    }

    private JSONObject parseJsonEntity(String jsonEntity) {
        if (StrUtil.isBlank(jsonEntity) || !JSONUtil.isTypeJSONObject(jsonEntity)) {
            return new JSONObject();
        }
        try {
            return JSONUtil.parseObj(jsonEntity);
        } catch (Exception e) {
            return new JSONObject();
        }
    }

    /**
     * 写扩散更新加盟商门店数量
     *
     * @param franchiseeId 加盟商ID
     * @param updateStoreNumTimestamp 更新水位
     * @param delta        变更值（+1 或 -1）
     */
    private void updateStoreNum(String franchiseeId,long updateStoreNumTimestamp, int delta) {
        if (StrUtil.isBlank(franchiseeId) || delta == 0) {
            return;
        }
        LambdaUpdateWrapper<FranchiseeEntity> updateWrapper = Wrappers.lambdaUpdate();
        updateWrapper.eq(FranchiseeEntity::getId, franchiseeId);
        updateWrapper.eq(FranchiseeEntity::getEnabledMark, 0);
        updateWrapper.lt(FranchiseeEntity::getUpdateStoreNumTimestamp, updateStoreNumTimestamp);//作为事件水位线。
        if (delta > 0) {
            updateWrapper.setSql("F_StoreNum = IFNULL(F_StoreNum, 0) + " + delta);
        } else {
            int abs = Math.abs(delta);
            updateWrapper.setSql("F_StoreNum = CASE WHEN IFNULL(F_StoreNum, 0) >= "
                    + abs + " THEN IFNULL(F_StoreNum, 0) - " + abs + " ELSE 0 END");
        }
        franchiseeMapper.update(null, updateWrapper);
    }

    /**
     * 切换租户数据源
     *
     * @param tenantId 租户ID
     */
    private void switchTenant(String tenantId) {
        // 判断是否为多租户
        if (!configValueUtil.isMultiTenancy()) {
            log.info("加盟商门店数量写扩散消息,配置为非多租户，跳过处理。tenantId={}", tenantId);
            return;
        }
        try {
            TenantDataSourceUtil.switchTenant(tenantId);
        } catch (Exception e) {
            throw new RuntimeException("切换租户失败", e);
        }
    }
}
