package shop.alien.store.service.clockin;

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.LifeClockInFeedApiResponse;
import shop.alien.entity.store.dto.LifeClockInFeedItemDto;
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 缓存与用户互动状态（拉黑、点赞、商户维度关注），行为对齐 {@link shop.alien.store.service.dynamics.DynamicsRecommendCacheService}。
 * <p>
 * 不替代数据库：须在 <strong>DB 已成功变更之后</strong> 由业务侧触发；缓存同步失败只记日志，不改变接口结果。
 * </p>
 */
@Slf4j
@Service
public class ClockInRecommendCacheService {

    private static final String LOCK_KEY_PREFIX = "clockInRecommendMutate:";

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

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

    public void onClockInLikeChanged(String likerUserIdParam, int clockInId, boolean nowLiked, int dianzanDelta) {
        Integer viewerId = resolveLifeUserId(likerUserIdParam);
        if (viewerId == null) {
            return;
        }
        mutateFeed(viewerId, feed -> {
            if (CollectionUtils.isEmpty(feed.getList())) {
                return;
            }
            for (LifeClockInFeedItemDto item : feed.getList()) {
                if (item.getId() != null && item.getId() == clockInId) {
                    item.setIsLike(nowLiked ? "1" : "0");
                    int base = item.getDianzanCount() == null ? 0 : item.getDianzanCount();
                    item.setDianzanCount(Math.max(0, base + dianzanDelta));
                    break;
                }
            }
        });
    }

    /**
     * 商户维度关注关系：刷新列表项中与门店 {@code phoneId} 匹配的 {@code isFollowThis}/{@code isFollowMe}。
     * （发布用户维度 {@code isPublisherFollowMe}/{@code isFollowPublisher} 暂不据此增量维护，与动态缓存范围一致。）
     */
    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);
            }
        }
    }

    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);
        });
    }

    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 (LifeClockInFeedItemDto 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 (LifeClockInFeedItemDto item : feed.getList()) {
                if (issuerPhoneId.equals(item.getPhoneId())) {
                    item.setIsFollowMe(flag);
                }
            }
        });
    }

    private void mutateFeed(int viewerLifeUserId, Consumer<LifeClockInFeedApiResponse> mutator) {
        String key = ClockInRecommendCacheConstants.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 = clockInFeedObjectMapper.readTree(json);
            JsonNode listNode = rootNode.get("list");
            int rawListLen = listNode != null && listNode.isArray() ? listNode.size() : 0;

            LifeClockInFeedApiResponse feed = clockInFeedObjectMapper.readValue(json, LifeClockInFeedApiResponse.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 = clockInFeedObjectMapper.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;
    }

    /** 打卡点赞 type=5 时由 {@link shop.alien.store.service.LifeCommentService} 调用。 */
    public void trySyncLikeCache(String userId, String huifuId, String type, boolean liked, int dianzanDelta) {
        if (!CommonConstant.LIKE_TYPE_CLOCK_IN.equals(type)) {
            return;
        }
        int clockInId = Integer.parseInt(huifuId.trim());
        onClockInLikeChanged(userId, clockInId, liked, dianzanDelta);
    }
}
