package shop.alien.store.service.dynamics;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import shop.alien.entity.store.LifeFans;
import shop.alien.entity.store.LifeUser;
import shop.alien.entity.store.dto.LifeDynamicsFeedApiResponse;
import shop.alien.entity.store.dto.LifeDynamicsFeedItemDto;
import shop.alien.mapper.LifeUserMapper;
import shop.alien.store.config.BaseRedisService;
import shop.alien.store.util.CommonConstant;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

/**
 * 维护「推荐动态」Redis 缓存与用户互动状态的一致性。
 * <p>
 * 不替代数据库：由业务在 <strong>DB 已成功提交相关变更之后</strong> 调用本服务做增量补丁；
 * 缓存缺失或同步失败不影响主流程返回值。
 * </p>
 * <p>
 * 仅在对应用户存在缓存 Key 时改写；无缓存则跳过（用户尚未拉过推荐或已过期）。
 * 写操作使用分布式锁 + 保留 TTL，降低并发下覆盖丢失的概率。
 * </p>
 */
@Slf4j
@Service
public class DynamicsRecommendCacheService {

    private static final String LOCK_KEY_PREFIX = "dynamicsRecommendMutate:";

    private final StringRedisTemplate stringRedisTemplate;
    private final BaseRedisService baseRedisService;
    private final LifeUserMapper lifeUserMapper;
    private final ObjectMapper dynamicsFeedObjectMapper;

    public DynamicsRecommendCacheService(StringRedisTemplate stringRedisTemplate,
                                         BaseRedisService baseRedisService,
                                         LifeUserMapper lifeUserMapper,
                                         ObjectMapper objectMapper) {
        this.stringRedisTemplate = stringRedisTemplate;
        this.baseRedisService = baseRedisService;
        this.lifeUserMapper = lifeUserMapper;
        this.dynamicsFeedObjectMapper = objectMapper.copy();
        this.dynamicsFeedObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    /**
     * 动态点赞 / 取消点赞后：同步当前用户在缓存列表中的 {@code is_like} 与 {@code dianzan_count}。
     */
    public void onDynamicsLikeChanged(String likerUserIdParam, int dynamicsId, boolean nowLiked, int dianzanDelta) {
        Integer viewerId = resolveLifeUserId(likerUserIdParam);
        if (viewerId == null) {
            return;
        }
        mutateFeed(viewerId, feed -> {
            if (CollectionUtils.isEmpty(feed.getList())) {
                return;
            }
            for (LifeDynamicsFeedItemDto item : feed.getList()) {
                if (item.getId() != null && item.getId() == dynamicsId) {
                    item.setIsLike(nowLiked ? "1" : "0");
                    int base = item.getDianzanCount() == null ? 0 : item.getDianzanCount();
                    item.setDianzanCount(Math.max(0, base + dianzanDelta));
                    break;
                }
            }
        });
    }

    /**
     * 关注关系变更：按粉丝双方身份刷新缓存中与 {@link LifeDynamicsFeedItemDto#getPhoneId()} 匹配的 {@code is_follow_this} / {@code is_follow_me}。
     *
     * @param followed {@code true} 建立关注；{@code false} 取消关注
     */
    public void onFollowRelationChanged(LifeFans fans, boolean followed) {
        if (fans == null || !StringUtils.hasText(fans.getFansId()) || !StringUtils.hasText(fans.getFollowedId())) {
            return;
        }
        String fansId = fans.getFansId().trim();
        String followedId = fans.getFollowedId().trim();

        if (fansId.startsWith("user_") && followedId.startsWith("store_")) {
            Integer viewerId = resolveLifeUserIdFromFansPhoneId(fansId);
            if (viewerId != null) {
                patchIsFollowThisByIssuerPhoneId(viewerId, followedId, followed);
            }
            return;
        }
        if (fansId.startsWith("store_") && followedId.startsWith("user_")) {
            Integer viewerId = resolveLifeUserIdFromFansPhoneId(followedId);
            if (viewerId != null) {
                patchIsFollowMeByIssuerPhoneId(viewerId, fansId, followed);
            }
            return;
        }
        if (fansId.startsWith("user_") && followedId.startsWith("user_")) {
            Integer viewerId = resolveLifeUserIdFromFansPhoneId(fansId);
            if (viewerId != null) {
                patchIsFollowThisByIssuerPhoneId(viewerId, followedId, followed);
            }
        }
    }

    /**
     * 用户拉黑商户：把被拉黑方 {@code store_user.id} 写入 {@link LifeDynamicsFeedApiResponse#getBlockedId()}。
     */
    public void addBlockedStore(int viewerLifeUserId, int storeUserId) {
        mutateFeed(viewerLifeUserId, feed -> {
            List<Integer> blocked = feed.getBlockedId();
            if (blocked == null) {
                blocked = new ArrayList<>();
            } else {
                blocked = new ArrayList<>(blocked);
            }
            if (!blocked.contains(storeUserId)) {
                blocked.add(storeUserId);
            }
            feed.setBlockedId(blocked);
        });
    }

    /**
     * 取消拉黑商户：从 {@code blocked_id} 中移除对应 {@code store_user.id}。
     */
    public void removeBlockedStore(int viewerLifeUserId, int storeUserId) {
        mutateFeed(viewerLifeUserId, feed -> {
            List<Integer> blocked = feed.getBlockedId();
            if (CollectionUtils.isEmpty(blocked)) {
                return;
            }
            List<Integer> next = new ArrayList<>(blocked.size());
            for (Integer id : blocked) {
                if (id != null && id != storeUserId) {
                    next.add(id);
                }
            }
            feed.setBlockedId(next);
        });
    }

    private void patchIsFollowThisByIssuerPhoneId(int viewerLifeUserId, String issuerPhoneId, boolean followed) {
        mutateFeed(viewerLifeUserId, feed -> {
            if (CollectionUtils.isEmpty(feed.getList())) {
                return;
            }
            String flag = followed ? "1" : "0";
            for (LifeDynamicsFeedItemDto item : feed.getList()) {
                if (issuerPhoneId.equals(item.getPhoneId())) {
                    item.setIsFollowThis(flag);
                }
            }
        });
    }

    private void patchIsFollowMeByIssuerPhoneId(int viewerLifeUserId, String issuerPhoneId, boolean followed) {
        mutateFeed(viewerLifeUserId, feed -> {
            if (CollectionUtils.isEmpty(feed.getList())) {
                return;
            }
            String flag = followed ? "1" : "0";
            for (LifeDynamicsFeedItemDto item : feed.getList()) {
                if (issuerPhoneId.equals(item.getPhoneId())) {
                    item.setIsFollowMe(flag);
                }
            }
        });
    }

    private void mutateFeed(int viewerLifeUserId, Consumer<LifeDynamicsFeedApiResponse> mutator) {
        String key = DynamicsRecommendCacheConstants.userFeedKey(viewerLifeUserId);
        String lockToken = baseRedisService.lock(LOCK_KEY_PREFIX + viewerLifeUserId, 8_000L, 5_000L);
        if (lockToken == null) {
            log.warn("推荐缓存未获取到锁，跳过更新 viewerLifeUserId={}", viewerLifeUserId);
            return;
        }
        try {
            String json = stringRedisTemplate.opsForValue().get(key);
            if (!StringUtils.hasText(json)) {
                return;
            }
            Long ttlSeconds = stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
            JsonNode rootNode = dynamicsFeedObjectMapper.readTree(json);
            JsonNode listNode = rootNode.get("list");
            int rawListLen = listNode != null && listNode.isArray() ? listNode.size() : 0;

            LifeDynamicsFeedApiResponse feed = dynamicsFeedObjectMapper.readValue(json, LifeDynamicsFeedApiResponse.class);
            int pojoListLen = feed.getList() == null ? 0 : feed.getList().size();
            if (rawListLen > 0 && pojoListLen == 0) {
                log.warn("推荐动态缓存：原始 JSON list 有 {} 条但反序列化为空，跳过写回以免清空 Redis viewerLifeUserId={}",
                        rawListLen, viewerLifeUserId);
                return;
            }
            if (rawListLen > pojoListLen) {
                log.warn("推荐动态缓存：list 反序列化不完整 raw={} pojo={}，跳过写回 viewerLifeUserId={}",
                        rawListLen, pojoListLen, viewerLifeUserId);
                return;
            }

            mutator.accept(feed);
            String out = dynamicsFeedObjectMapper.writeValueAsString(feed);
            stringRedisTemplate.opsForValue().set(key, out);
            if (ttlSeconds != null && ttlSeconds > 0) {
                stringRedisTemplate.expire(key, ttlSeconds, TimeUnit.SECONDS);
            }
        } catch (Exception e) {
            log.warn("推荐缓存读写失败 viewerLifeUserId={}", viewerLifeUserId, e);
        } finally {
            baseRedisService.unlock(LOCK_KEY_PREFIX + viewerLifeUserId, lockToken);
        }
    }

    private Integer resolveLifeUserId(String userIdParam) {
        if (!StringUtils.hasText(userIdParam)) {
            return null;
        }
        String t = userIdParam.trim();
        if (t.matches("^\\d+$")) {
            try {
                return Integer.valueOf(t);
            } catch (NumberFormatException ignored) {
                return null;
            }
        }
        return resolveLifeUserIdFromFansPhoneId(t.startsWith("user_") ? t : "user_" + t);
    }

    private Integer resolveLifeUserIdFromFansPhoneId(String fansPhoneId) {
        if (!StringUtils.hasText(fansPhoneId) || !fansPhoneId.startsWith("user_")) {
            return null;
        }
        String phone = fansPhoneId.substring("user_".length());
        if (!StringUtils.hasText(phone)) {
            return null;
        }
        LifeUser u = lifeUserMapper.selectOne(new LambdaQueryWrapper<LifeUser>()
                .eq(LifeUser::getUserPhone, phone)
                .eq(LifeUser::getDeleteFlag, 0)
                .last("LIMIT 1"));
        return u != null ? u.getId() : null;
    }

    /**
     * 供点赞入口调用：若为社区动态类型则刷新缓存。
     */
    public void trySyncLikeCache(String userId, String huifuId, String type, boolean liked, int dianzanDelta) {
        if (!CommonConstant.LIKE_TYPE_DYNAMICS.equals(type)) {
            return;
        }
        int dynamicsId = Integer.parseInt(huifuId.trim());
        onDynamicsLikeChanged(userId, dynamicsId, liked, dianzanDelta);
    }
}
