package com.liquidnet.service.kylin.service.impl;

import com.github.pagehelper.PageInfo;
import com.liquidnet.common.cache.redis.util.RedisUtil;
import com.liquidnet.commons.lang.util.*;
import com.liquidnet.service.adam.constant.AdamRedisConst;
import com.liquidnet.service.adam.dto.vo.AdamUserInfoVo;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.base.SqlMapping;
import com.liquidnet.service.base.constant.MQConst;
import com.liquidnet.service.base.constant.RedisKeyExpireConst;
import com.liquidnet.service.kylin.constant.GroupBuyStatusConst;
import com.liquidnet.service.kylin.constant.KylinRedisConst;
import com.liquidnet.service.kylin.dto.vo.groupbuy.*;
import com.liquidnet.service.kylin.dto.vo.middle.KylinTicketTimesVo;
import com.liquidnet.service.kylin.dto.vo.middle.KylinTicketVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinOrderTicketEntitiesVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinOrderTicketVo;
import com.liquidnet.service.kylin.dto.vo.mongo.KylinPerformanceVo;
import com.liquidnet.service.kylin.service.IKylinGroupBuyOrderService;
import com.liquidnet.service.kylin.utils.DataUtils;
import com.liquidnet.service.kylin.utils.QueueUtils;
import com.mongodb.BasicDBObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.util.*;

@Service
@Slf4j
public class KylinGroupBuyOrderServiceImpl implements IKylinGroupBuyOrderService {

    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private DataUtils dataUtils;
    @Autowired
    private KylinGroupBuyActivityServiceImpl activityService;
    @Autowired
    private KylinOrderTicketsRefundServiceImpl refundService;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private QueueUtils queueUtils;

    @Override
    public ResponseDto<List<KylinGroupBuyOrderCardVo>> pending(String activityId, String timeId, String ticketId) {
        Query query = Query.query(Criteria.where("activityId").is(activityId)
                .and("ticketId").is(ticketId)
                .and("timeId").is(timeId)
                .and("status").is(GroupBuyStatusConst.OrderStatus.PENDING)
                .and("isPublic").is(GroupBuyStatusConst.PublicFlag.PUBLIC));
        query.with(Sort.by(Sort.Direction.ASC, "createdAt")).limit(3);
        List<KylinGroupBuyOrderVo> orders = mongoTemplate.find(query,
                KylinGroupBuyOrderVo.class, KylinGroupBuyOrderVo.class.getSimpleName());
        List<KylinGroupBuyOrderCardVo> result = new ArrayList<>();
        for (KylinGroupBuyOrderVo o : orders) {
            if (o.getCurrentCount() >= o.getTargetCount()) continue;
            if (isExpired(o)) continue;
            KylinGroupBuyOrderCardVo card = new KylinGroupBuyOrderCardVo();
            card.setGroupOrderId(o.getGroupOrderId());
            card.setActivityId(o.getActivityId());
            card.setLeaderUserId(o.getLeaderUserId());
            card.setTargetCount(o.getTargetCount());
            card.setCurrentCount(o.getCurrentCount());
            card.setShareToken(o.getShareToken());
            result.add(card);
        }
        return ResponseDto.success(result);
    }

    @Override
    public ResponseDto<PageInfo<KylinGroupBuyMyGroupVo>> my(Integer page, Integer size) {
        String uid = CurrentUtil.getCurrentUid();
        Criteria criteria = new Criteria().orOperator(
                Criteria.where("leaderUserId").is(uid),
                Criteria.where("members.userId").is(uid));
        Query countQuery = Query.query(criteria);
        long total = mongoTemplate.count(countQuery, KylinGroupBuyOrderVo.class,
                KylinGroupBuyOrderVo.class.getSimpleName());
        Query dataQuery = Query.query(criteria).with(Sort.by(Sort.Direction.DESC, "createdAt"));
        if (page != null && size != null && page > 0 && size > 0) {
            dataQuery.skip((long) (page - 1) * size).limit(size);
        }
        List<KylinGroupBuyOrderVo> orders = mongoTemplate.find(dataQuery,
                KylinGroupBuyOrderVo.class, KylinGroupBuyOrderVo.class.getSimpleName());
        List<KylinGroupBuyMyGroupVo> result = new ArrayList<>();
        Map<String, String> avatarCache = new HashMap<>();
        for (KylinGroupBuyOrderVo o : orders) {
            KylinGroupBuyMyGroupVo vo = new KylinGroupBuyMyGroupVo();
            vo.setGroupOrderId(o.getGroupOrderId());
            vo.setActivityId(o.getActivityId());
            vo.setLeaderUserId(o.getLeaderUserId());
            vo.setTargetCount(o.getTargetCount());
            vo.setCurrentCount(o.getCurrentCount());
            vo.setStatus(o.getStatus());
            vo.setExpiresAt(o.getExpiresAt());
            vo.setIsLeader(uid.equals(o.getLeaderUserId()) ? 1 : 0);
            if (o.getMembers() != null) {
                List<GroupBuyMemberUserInfoVo> memberUserList = new ArrayList<>();
                for (KylinGroupBuyMemberVo m : o.getMembers()) {
                    GroupBuyMemberUserInfoVo b = new GroupBuyMemberUserInfoVo();
                    b.setUserId(m.getUserId());
                    b.setAvatar(resolveAvatar(m.getUserId(), m.getAvatar(), avatarCache));
                    b.setNickname(m.getNickname());
                    memberUserList.add(b);
                }
                vo.setMemberBriefs(memberUserList);
            }
            result.add(vo);
        }
        PageInfo<KylinGroupBuyMyGroupVo> pageInfo = new PageInfo<>(result);
        pageInfo.setTotal(total);
        return ResponseDto.success(pageInfo);
    }

    @Override
    public ResponseDto<KylinGroupBuyOrderDetailVo> detail(String groupOrderId, String shareToken) {
        KylinGroupBuyOrderVo order = getOrderForJoin(groupOrderId, shareToken);
        if (order == null) return ResponseDto.failure("拼团不存在");
        KylinGroupBuyActivityVo activity = findActivity(order.getActivityId());
        if (null == activity) {
            return ResponseDto.failure("拼团活动不存在");
        }
        KylinPerformanceVo performance = dataUtils.getPerformanceVo(order.getActivityId() != null ? activity.getPerformanceId() : null);

        KylinGroupBuyOrderDetailVo vo = new KylinGroupBuyOrderDetailVo();
        vo.setGroupOrderId(order.getGroupOrderId());
        vo.setActivityId(order.getActivityId());
        vo.setLeaderUserId(order.getLeaderUserId());
        vo.setTargetCount(order.getTargetCount());
        vo.setCurrentCount(order.getCurrentCount());
        vo.setStatus(order.getStatus());
        vo.setIsPublic(order.getIsPublic());
        vo.setShareToken(order.getShareToken());
        vo.setExpiresAt(order.getExpiresAt());
        vo.setCreatedAt(order.getCreatedAt());
        vo.setTimeId(order.getTimeId());
        vo.setTicketId(order.getTicketId());
        String currentUid = CurrentUtil.getCurrentUid();
        vo.setIsLeader(StringUtil.isNotBlank(currentUid) && currentUid.equals(order.getLeaderUserId()) ? 1 : 0);

        if (performance != null) {
            vo.setPerformanceName(performance.getTitle());
            fillTicketInfo(performance, order.getTimeId(), order.getTicketId(), vo);
        }
        if (activity != null) {
            vo.setPerformanceId(activity.getPerformanceId());
            KylinGroupBuyDiscountVo discount = activityService.findDiscount(activity, order.getTimeId(), order.getTicketId());
            vo.setGroupBuyPrice(discount != null ? discount.getDiscountValue() : null);
        }
        if (order.getMembers() != null) {
            Map<String, String> avatarCache = new HashMap<>();
            List<GroupBuyMemberUserInfoVo> members = new ArrayList<>();
            for (KylinGroupBuyMemberVo m : order.getMembers()) {
                GroupBuyMemberUserInfoVo mu = new GroupBuyMemberUserInfoVo();
                mu.setUserId(m.getUserId());
                mu.setNickname(m.getNickname());
                mu.setAvatar(resolveAvatar(m.getUserId(), m.getAvatar(), avatarCache));
                members.add(mu);
            }
            vo.setMembers(members);
        }
        return ResponseDto.success(vo);
    }

    @Override
    public ResponseDto<Boolean> cancel(String groupOrderId) {
        String uid = CurrentUtil.getCurrentUid();
        KylinGroupBuyOrderVo order = findOrder(groupOrderId);
        if (order == null) return ResponseDto.failure("拼团不存在");
        if (!uid.equals(order.getLeaderUserId())) return ResponseDto.failure("仅团长可取消拼团");
        if (GroupBuyStatusConst.OrderStatus.SUCCESS.equals(order.getStatus()))
            return ResponseDto.failure("拼团已成，不可取消");
        if (!GroupBuyStatusConst.OrderStatus.PENDING.equals(order.getStatus()))
            return ResponseDto.failure("当前状态不可取消");

        String lockKey = KylinRedisConst.GROUP_BUY_ORDER_LOCK.concat(groupOrderId);
        if (!redisUtil.lock(lockKey, 1, 15)) {
            return ResponseDto.failure("拼团处理中，请稍后重试");
        }
        List<KylinGroupBuyMemberVo> joinedToRefund = new ArrayList<>();
        try {
            // 持锁后回源，仅 PENDING → CANCELLED
            order = mongoTemplate.findOne(
                    Query.query(Criteria.where("groupOrderId").is(groupOrderId)),
                    KylinGroupBuyOrderVo.class, KylinGroupBuyOrderVo.class.getSimpleName());
            if (order == null) return ResponseDto.failure("拼团不存在");
            if (!GroupBuyStatusConst.OrderStatus.PENDING.equals(order.getStatus())) {
                return ResponseDto.failure("当前状态不可取消");
            }
            order.setStatus(GroupBuyStatusConst.OrderStatus.CANCELLED);
            order.setUpdatedAt(now());
            if (!CollectionUtils.isEmpty(order.getMembers())) {
                for (KylinGroupBuyMemberVo member : order.getMembers()) {
                    if (GroupBuyStatusConst.MemberStatus.JOINED.equals(member.getStatus())) {
                        joinedToRefund.add(member);
                        member.setStatus(GroupBuyStatusConst.MemberStatus.REFUNDED);
                    } else if (GroupBuyStatusConst.MemberStatus.PENDING_PAY.equals(member.getStatus())) {
                        member.setStatus(GroupBuyStatusConst.MemberStatus.CANCELLED);
                    }
                }
            }
            order.setCurrentCount(0);
            saveOrder(order);
        } finally {
            redisUtil.uLock(lockKey);
        }
        for (KylinGroupBuyMemberVo member : joinedToRefund) {
            refundJoinedMember(member.getOrderTicketsId(), "团长取消拼团，全额退款");
        }
        return ResponseDto.success(true);
    }

    @Override
    public ResponseDto<KylinGroupBuyRefreshShareVo> refreshShare(String groupOrderId) {
        String uid = CurrentUtil.getCurrentUid();
        KylinGroupBuyOrderVo order = findOrder(groupOrderId);
        if (order == null) return ResponseDto.failure("拼团不存在");
        if (!uid.equals(order.getLeaderUserId())) return ResponseDto.failure("仅团长可刷新邀请链接");
        if (!GroupBuyStatusConst.OrderStatus.PENDING.equals(order.getStatus())
                && !GroupBuyStatusConst.OrderStatus.PREPARE.equals(order.getStatus())) {
            return ResponseDto.failure("当前状态不可刷新邀请链接");
        }
        redisUtil.del(KylinRedisConst.GROUP_BUY_ORDER_SHARE.concat(order.getShareToken()));
        order.setShareToken(IDGenerator.nextSnowId());
        order.setUpdatedAt(now());
        saveOrder(order);
        KylinGroupBuyRefreshShareVo vo = new KylinGroupBuyRefreshShareVo();
        vo.setGroupOrderId(groupOrderId);
        vo.setShareToken(order.getShareToken());
        return ResponseDto.success(vo);
    }

    // ==================== private helpers ====================

    private KylinGroupBuyOrderVo getOrderForJoin(String groupOrderId, String shareToken) {
        if (shareToken != null) {
            Object v = redisUtil.get(KylinRedisConst.GROUP_BUY_ORDER_SHARE.concat(shareToken));
            if (v != null) return (KylinGroupBuyOrderVo) v;
        } else if (groupOrderId != null) {
            Object v = redisUtil.get(KylinRedisConst.GROUP_BUY_ORDER.concat(groupOrderId));
            if (v != null) return (KylinGroupBuyOrderVo) v;
        }
        Criteria c = shareToken != null ? Criteria.where("shareToken").is(shareToken)
                : Criteria.where("groupOrderId").is(groupOrderId);
        KylinGroupBuyOrderVo order = mongoTemplate.findOne(Query.query(c),
                KylinGroupBuyOrderVo.class, KylinGroupBuyOrderVo.class.getSimpleName());
        cacheOrder(order);
        return order;
    }

    private KylinGroupBuyOrderVo findOrder(String groupOrderId) {
        if (groupOrderId == null) return null;
        Object obj = redisUtil.get(KylinRedisConst.GROUP_BUY_ORDER.concat(groupOrderId));
        if (obj != null) return (KylinGroupBuyOrderVo) obj;
        KylinGroupBuyOrderVo order = mongoTemplate.findOne(
                Query.query(Criteria.where("groupOrderId").is(groupOrderId)),
                KylinGroupBuyOrderVo.class, KylinGroupBuyOrderVo.class.getSimpleName());
        cacheOrder(order);
        return order;
    }

    private KylinGroupBuyActivityVo findActivity(String activityId) {
        Object obj = redisUtil.get(KylinRedisConst.GROUP_BUY_ACTIVITY.concat(activityId));
        if (obj != null) return (KylinGroupBuyActivityVo) obj;
        KylinGroupBuyActivityVo activity = mongoTemplate.findOne(
                Query.query(Criteria.where("activityId").is(activityId)),
                KylinGroupBuyActivityVo.class, KylinGroupBuyActivityVo.class.getSimpleName());
        if (activity != null) redisUtil.set(KylinRedisConst.GROUP_BUY_ACTIVITY.concat(activityId),
                activity, RedisKeyExpireConst.KYLIN_GROUP_BUY_EXPIRE);
        return activity;
    }

    private void saveOrder(KylinGroupBuyOrderVo order) {
        clearOrderCache(order);
        mongoTemplate.getCollection(KylinGroupBuyOrderVo.class.getSimpleName()).updateOne(
                Query.query(Criteria.where("groupOrderId").is(order.getGroupOrderId())).getQueryObject(),
                new BasicDBObject().append("$set", mongoTemplate.getConverter().convertToMongoType(order)));
        sendReplaceOrderSql(order, MQConst.KylinQueue.SQL_ORDER_PAY.getKey());
        cacheOrder(order);
    }

    private void sendReplaceOrderSql(KylinGroupBuyOrderVo order, String queueKey) {
        if (order.getOrderTicketsId() == null) return;
        LinkedList<String> sqls = CollectionUtil.linkedListString();
        LinkedList<Object[]> od = CollectionUtil.linkedListObjectArr();
        LinkedList<Object[]> oi = CollectionUtil.linkedListObjectArr();
        LinkedList<Object[]> md = CollectionUtil.linkedListObjectArr();
        LinkedList<Object[]> mi = CollectionUtil.linkedListObjectArr();
        sqls.add(SqlMapping.get("kylin_group_buy_order.delete"));
        od.add(new Object[]{order.getGroupOrderId()});
        sqls.add(SqlMapping.get("kylin_group_buy_order.insert"));
        oi.add(orderInsertObject(order));
        sqls.add(SqlMapping.get("kylin_group_buy_member.delete_by_group"));
        md.add(new Object[]{order.getGroupOrderId()});
        if (!CollectionUtils.isEmpty(order.getMembers())) {
            sqls.add(SqlMapping.get("kylin_group_buy_member.insert"));
            for (KylinGroupBuyMemberVo m : order.getMembers()) mi.add(memberInsertObject(m));
            queueUtils.sendMsgByRedis(queueKey, SqlMapping.gets(sqls, od, oi, md, mi));
        } else {
            queueUtils.sendMsgByRedis(queueKey, SqlMapping.gets(sqls, od, oi, md));
        }
    }

    private Object[] orderInsertObject(KylinGroupBuyOrderVo vo) {
        return new Object[]{vo.getGroupOrderId(), vo.getActivityId(), vo.getLeaderUserId(),
                vo.getOrderTicketsId(), vo.getTargetCount(), vo.getCurrentCount(), vo.getStatus(),
                vo.getIsPublic(), vo.getShareToken(),
                vo.getExpiresAt() == null ? null : DateUtil.Formatter.yyyyMMddHHmmss.parse(vo.getExpiresAt()),
                DateUtil.Formatter.yyyyMMddHHmmss.parse(vo.getCreatedAt()),
                DateUtil.Formatter.yyyyMMddHHmmss.parse(vo.getUpdatedAt())};
    }

    private Object[] memberInsertObject(KylinGroupBuyMemberVo vo) {
        return new Object[]{vo.getMemberId(), vo.getGroupOrderId(), vo.getUserId(),
                vo.getOrderTicketsId(), vo.getTicketCount(), vo.getStatus(),
                DateUtil.Formatter.yyyyMMddHHmmss.parse(vo.getJoinTime()),
                DateUtil.Formatter.yyyyMMddHHmmss.parse(vo.getCreatedAt())};
    }

    private void cacheOrder(KylinGroupBuyOrderVo order) {
        if (order == null) return;
        redisUtil.set(KylinRedisConst.GROUP_BUY_ORDER.concat(order.getGroupOrderId()), order,
                RedisKeyExpireConst.KYLIN_GROUP_BUY_EXPIRE);
        redisUtil.set(KylinRedisConst.GROUP_BUY_ORDER_SHARE.concat(order.getShareToken()), order,
                RedisKeyExpireConst.KYLIN_GROUP_BUY_EXPIRE);
    }

    private void clearOrderCache(KylinGroupBuyOrderVo order) {
        if (order == null) return;
        redisUtil.del(KylinRedisConst.GROUP_BUY_ORDER.concat(order.getGroupOrderId()));
        redisUtil.del(KylinRedisConst.GROUP_BUY_ORDER_SHARE.concat(order.getShareToken()));
    }

    private boolean isExpired(KylinGroupBuyOrderVo order) {
        return order.getExpiresAt() != null
                && DateUtil.Formatter.yyyyMMddHHmmss.parse(order.getExpiresAt()).isBefore(LocalDateTime.now());
    }

    private long restSeconds(KylinGroupBuyOrderVo order) {
        if (order.getExpiresAt() == null) return 0L;
        return Math.max(0L, java.time.Duration.between(LocalDateTime.now(),
                DateUtil.Formatter.yyyyMMddHHmmss.parse(order.getExpiresAt())).getSeconds());
    }

    private void refundJoinedMember(String orderTicketsId, String reason) {
        KylinOrderTicketVo ticketVo = dataUtils.getOrderTicketVo(orderTicketsId);
        if (ticketVo == null || CollectionUtils.isEmpty(ticketVo.getEntitiesVoList())) {
            return;
        }
        for (KylinOrderTicketEntitiesVo entity : ticketVo.getEntitiesVoList()) {
            try {
                refundService.systemFullRefund(ticketVo.getOrderTicketsId(),
                        entity.getOrderTicketEntitiesId(), reason);
            } catch (Exception e) {
                log.error("拼团散团退款失败 orderTicketsId={}, entityId={}",
                        orderTicketsId, entity.getOrderTicketEntitiesId(), e);
            }
        }
    }

    private String now() {
        return DateUtil.Formatter.yyyyMMddHHmmss.format(LocalDateTime.now());
    }

    /**
     * 快照头像为空时，从 adam 用户缓存回填（不回写 Mongo）。
     */
    private String resolveAvatar(String userId, String avatar, Map<String, String> cache) {
        if (StringUtil.isNotBlank(avatar)) {
            return avatar;
        }
        if (StringUtil.isBlank(userId)) {
            return "";
        }
        if (cache != null && cache.containsKey(userId)) {
            return cache.get(userId);
        }
        String filled = "";
        try {
            Object obj = redisUtil.get(AdamRedisConst.INFO_USER.concat(userId));
            if (obj instanceof AdamUserInfoVo) {
                String fromCache = ((AdamUserInfoVo) obj).getAvatar();
                filled = fromCache == null ? "" : fromCache;
            }
        } catch (Exception e) {
            log.warn("拼团回填头像失败 userId={}", userId, e);
        }
        if (cache != null) {
            cache.put(userId, filled);
        }
        return filled;
    }

    private void fillTicketInfo(KylinPerformanceVo p, String timeId, String ticketId,
                                KylinGroupBuyOrderDetailVo vo) {
        if (p.getTicketTimeList() == null) return;
        for (KylinTicketTimesVo t : p.getTicketTimeList()) {
            if (!timeId.equals(t.getTimeId())) continue;
            vo.setTimeTitle(t.getTitle());
            if (t.getTicketList() == null) return;
            for (KylinTicketVo tk : t.getTicketList()) {
                if (ticketId.equals(tk.getTicketsId())) {
                    vo.setTicketName(tk.getTitle());
                    vo.setOriginalPrice(tk.getPrice());
                    return;
                }
            }
            return;
        }
    }

}
