package com.gxwebsoft.glt.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.gxwebsoft.glt.entity.GltTicketTemplate;
import com.gxwebsoft.glt.entity.GltUserTicket;
import com.gxwebsoft.glt.entity.GltUserTicketLog;
import com.gxwebsoft.glt.entity.GltUserTicketRelease;
import com.gxwebsoft.glt.task.DealerOrderSettlement10584Task;
import com.gxwebsoft.shop.entity.ShopOrder;
import com.gxwebsoft.shop.entity.ShopOrderGoods;
import com.gxwebsoft.shop.mapper.ShopOrderMapper;
import com.gxwebsoft.shop.service.ShopOrderGoodsService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 套票发放（从订单生成用户套票 + 释放计划）的业务逻辑。
 *
 * 说明：
 * - 定时任务无登录态时，MyBatis-Plus 多租户插件可能拿不到 tenantId；
 *   外层任务方法会通过 @IgnoreTenant 禁用租户拦截，本服务内部强制用 tenantId 过滤。
 * - 幂等：以 (tenantId, templateId, orderNo, orderGoodsId) 判断是否已发放。
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class GltTicketIssueService {

    public static final int CHANGE_TYPE_ISSUE = 10;

    private enum IssueOutcome {
        ISSUED,
        ALREADY_ISSUED,
        SKIPPED,
        NO_TEMPLATE
    }

    private final ShopOrderMapper shopOrderMapper;
    private final ShopOrderGoodsService shopOrderGoodsService;

    private final GltTicketTemplateService gltTicketTemplateService;
    private final GltUserTicketService gltUserTicketService;
    private final GltUserTicketReleaseService gltUserTicketReleaseService;
    private final GltUserTicketLogService gltUserTicketLogService;
    private final TransactionTemplate transactionTemplate;
    private final DealerOrderSettlement10584Task dealerOrderSettlement;

    /**
     * 扫描“今日订单”，执行套票发放。
     */
    public void issueTodayOrders(Integer tenantId, Integer formId) {
        if (formId == null) {
            return;
        }
        issueTodayOrders(tenantId, List.of(formId));
    }

    /**
     * 扫描“今日订单”，执行套票发放（支持多个商品/模板）。
     */
    public void issueTodayOrders(Integer tenantId, List<Integer> goodsIds) {
        if (tenantId == null || goodsIds == null || goodsIds.isEmpty()) {
            return;
        }
        List<Integer> uniqueGoodsIds = goodsIds.stream()
            .filter(Objects::nonNull)
            .distinct()
            .toList();
        if (uniqueGoodsIds.isEmpty()) {
            return;
        }
        Set<Integer> goodsIdSet = new HashSet<>(uniqueGoodsIds);

        LocalDateTime todayStart = LocalDate.now().atStartOfDay();
        LocalDateTime tomorrowStart = todayStart.plusDays(1);

        List<ShopOrder> orders = shopOrderMapper.selectList(
            new LambdaQueryWrapper<ShopOrder>()
                .eq(ShopOrder::getTenantId, tenantId)
                .in(ShopOrder::getFormId, uniqueGoodsIds)
                .eq(ShopOrder::getPayStatus, true)
                .eq(ShopOrder::getOrderStatus, 0)
                // 今日订单（兼容：以 create_time 或 pay_time 任一落在今日即可）
                .and(w -> w
                    .ge(ShopOrder::getCreateTime, todayStart).lt(ShopOrder::getCreateTime, tomorrowStart)
                    .or()
                    .ge(ShopOrder::getPayTime, todayStart).lt(ShopOrder::getPayTime, tomorrowStart)
                )
                .orderByAsc(ShopOrder::getPayTime)
                .orderByAsc(ShopOrder::getOrderId)
        );

        if (orders.isEmpty()) {
            log.debug("套票发放扫描：今日无符合条件的订单 tenantId={}, goodsIds={}", tenantId, uniqueGoodsIds);
            return;
        }

        int success = 0;
        int skipped = 0;
        int failed = 0;

        for (ShopOrder order : orders) {
            try {
                int issuedCount = issueForOrder(tenantId, goodsIdSet, order);
                if (issuedCount > 0) {
                    success += issuedCount;
                } else {
                    skipped++;
                }
            } catch (Exception e) {
                failed++;
                log.error("套票发放失败 - tenantId={}, orderNo={}, orderId={}",
                    tenantId, order.getOrderNo(), order.getOrderId(), e);
            }
        }

        log.info("套票发放扫描完成 - tenantId={}, goodsIds={}, 订单数={}, 发放成功={}, 跳过={}, 失败={}",
            tenantId, uniqueGoodsIds, orders.size(), success, skipped, failed);
    }

    /**
     * 商品订单支付成功后调后需处理业务
     * @param orderNo   订单号
     * @param tenantId  租户ID
     */
    @Async
    public void paySuccessTask(String orderNo, Integer tenantId){
        //1.发送水票
        suerTicketRelease(orderNo, tenantId);

        //2.执行分销员分销、统计门店/服务商分销业务
        dealerOrderSettlement.orderSettlement(orderNo);

        //3.执行平台分红业务 TODO 待开发

    }

    /**
     * 订单支付成功，直接发送水票【后期优化订单类型，为水票的订单才需要执行此业务】
     * @param orderNo   订单号
     * @param tenantId  租户ID
     */
    @Transactional
    public void suerTicketRelease(String orderNo, Integer tenantId){
        //1.订单为空跳过执行
        ShopOrder shopOrder = shopOrderMapper.selectOne(new LambdaQueryWrapper<ShopOrder>()
                .eq(ShopOrder::getOrderNo, orderNo)
                .eq(ShopOrder::getTenantId, tenantId));
        if(shopOrder == null){
            return;
        }

        //2.只有水票订单才需要发送水票
        if(!(shopOrder.getWaterTicketFlag() != null && shopOrder.getWaterTicketFlag() == 1)){
            return;
        }

        //3.跳过已完成发放套票订单
        if(shopOrder.getOrderStatus() == 1){
            return;
        }

        //4.订单商品为空跳过执行
        List<ShopOrderGoods> goodsList = shopOrderGoodsService.getListByOrderIdIgnoreTenant(shopOrder.getOrderId());
        if (CollectionUtils.isEmpty(goodsList)) {
            return;
        }

        //5.执行水票发放业务
        AtomicBoolean release = new AtomicBoolean(false);
        goodsList.forEach(orderGood ->{
            IssueOutcome outcome = transactionTemplate.execute(status -> doIssueOne(tenantId, shopOrder, orderGood));
            if(Arrays.asList(IssueOutcome.ISSUED, IssueOutcome.ALREADY_ISSUED).contains(outcome)){
                release.set(true);
            }
        });

        //6.更新商品订单为已完成、已收到赠品状态
        if (release.get()) {
            shopOrder.setHasTakeGift(true);
            shopOrder.setUpdateTime(LocalDateTime.now());
            shopOrderMapper.updateById(shopOrder);
        }
    }

    private int issueForOrder(Integer tenantId, Set<Integer> goodsIds, ShopOrder order) {
        List<ShopOrderGoods> goodsList = shopOrderGoodsService.getListByOrderIdIgnoreTenant(order.getOrderId());
        if (goodsList == null || goodsList.isEmpty()) {
            return 0;
        }

        int issuedCount = 0; // 本轮新增发放数量（用于统计）
        boolean shouldCompleteOrder = false;

        for (ShopOrderGoods og : goodsList) {
            if (og.getGoodsId() == null || !goodsIds.contains(og.getGoodsId())) {
                continue;
            }

            IssueOutcome outcome = transactionTemplate.execute(status -> doIssueOne(tenantId, order, og));
            if (outcome == IssueOutcome.ISSUED) {
                issuedCount++;
                shouldCompleteOrder = true;
            } else if (outcome == IssueOutcome.ALREADY_ISSUED) {
                // 幂等：已处理过也应视为完成，避免重复扫描
                shouldCompleteOrder = true;
            }
        }

        if (shouldCompleteOrder) {
            LocalDateTime now = LocalDateTime.now();
            // 任务执行完后将订单置为“已完成”，避免后续扫描重复处理（幂等虽可挡住，但会产生大量无意义查询）。
            order.setOrderStatus(1);
            order.setHasTakeGift(true);
            order.setUpdateTime(LocalDateTime.now());
            shopOrderMapper.updateById(order);
        }

        return issuedCount;
    }

    private IssueOutcome doIssueOne(Integer tenantId, ShopOrder order, ShopOrderGoods og) {
        if (order.getUserId() == null) {
            log.warn("套票发放跳过：订单缺少 userId - tenantId={}, orderNo={}", tenantId, order.getOrderNo());
            return IssueOutcome.SKIPPED;
        }
        if (og.getId() == null) {
            log.warn("套票发放跳过：订单商品缺少 id - tenantId={}, orderNo={}", tenantId, order.getOrderNo());
            return IssueOutcome.SKIPPED;
        }

        // 并发幂等兜底：
        // - 该任务可能在多实例部署下并发执行；若不加锁，在没有唯一索引的情况下可能重复发放/重复核销。
        // - 这里先对商城订单行加行锁，保证同一订单在同一时刻只会被一个事务处理。
        //   (注意：需数据库支持 SELECT ... FOR UPDATE，且 shop_order.order_id 为主键/有索引)
        if (order.getOrderId() != null) {
            shopOrderMapper.selectOne(
                new LambdaQueryWrapper<ShopOrder>()
                    .eq(ShopOrder::getOrderId, order.getOrderId())
                    .eq(ShopOrder::getTenantId, tenantId)
                    .last("for update")
            );
        }

        // 同一商品允许存在多条模板记录（历史数据/误操作）。为避免取到“错误模板”，这里做确定性排序取第一条。
        // 排序规则与后台 getByGoodsId 保持一致：sortNumber 越小越靠前，其次取最新创建时间。
        GltTicketTemplate template = gltTicketTemplateService.getOne(
            new LambdaQueryWrapper<GltTicketTemplate>()
                .eq(GltTicketTemplate::getTenantId, tenantId)
                .eq(GltTicketTemplate::getGoodsId, og.getGoodsId())
                .eq(GltTicketTemplate::getDeleted, 0)
                .orderByAsc(GltTicketTemplate::getSortNumber)
                .orderByDesc(GltTicketTemplate::getCreateTime)
                .orderByDesc(GltTicketTemplate::getId)
                .last("limit 1")
        );

        if (template == null) {
            log.warn("套票发放跳过：未配置套票模板 - tenantId={}, goodsId={}, orderNo={}",
                tenantId, og.getGoodsId(), order.getOrderNo());
            return IssueOutcome.NO_TEMPLATE;
        }
        if (!Boolean.TRUE.equals(template.getEnabled())) {
            log.info("套票发放跳过：套票模板未启用 - tenantId={}, templateId={}, goodsId={}",
                tenantId, template.getId(), template.getGoodsId());
            return IssueOutcome.SKIPPED;
        }

        // 幂等：同一 orderNo + orderGoodsId 只发放一次
        GltUserTicket existing = gltUserTicketService.getOne(
            new LambdaQueryWrapper<GltUserTicket>()
                .eq(GltUserTicket::getTenantId, tenantId)
                .eq(GltUserTicket::getTemplateId, template.getId())
                .eq(GltUserTicket::getOrderNo, order.getOrderNo())
                .eq(GltUserTicket::getOrderGoodsId, og.getId())
                .eq(GltUserTicket::getDeleted, 0)
                .last("limit 1")
        );
        if (existing != null) {
            log.debug("套票已发放，跳过 - tenantId={}, orderNo={}, orderGoodsId={}, userTicketId={}",
                tenantId, order.getOrderNo(), og.getId(), existing.getId());
            return IssueOutcome.ALREADY_ISSUED;
        }

        int buyQty = og.getTotalNum() != null ? og.getTotalNum() : 0;
        if (buyQty <= 0) {
            log.warn("套票发放跳过：购买数量无效 - tenantId={}, orderNo={}, orderGoodsId={}, totalNum={}",
                tenantId, order.getOrderNo(), og.getId(), og.getTotalNum());
            return IssueOutcome.SKIPPED;
        }

        Integer minBuyQty = template.getMinBuyQty();
        if (minBuyQty != null && minBuyQty > 0 && buyQty < minBuyQty) {
            log.info("套票发放跳过：未达到最小购买数量 - tenantId={}, orderNo={}, buyQty={}, minBuyQty={}",
                tenantId, order.getOrderNo(), buyQty, minBuyQty);
            return IssueOutcome.SKIPPED;
        }

        int giftMultiplier = template.getGiftMultiplier() != null ? template.getGiftMultiplier() : 0;
        int giftQty = buyQty * Math.max(giftMultiplier, 0);

        // 总票数（购买量 + 赠送量）
        int totalQty = buyQty + giftQty;

        if (totalQty <= 0) {
            log.info("套票发放跳过：计算结果为0 - tenantId={}, orderNo={}, buyQty={}, giftMultiplier={}, includeBuyQty={}",
                tenantId, order.getOrderNo(), buyQty, giftMultiplier, template.getIncludeBuyQty());
            return IssueOutcome.SKIPPED;
        }

        LocalDateTime now = LocalDateTime.now();

        boolean useReleasePeriods = template.getReleasePeriods() != null && template.getReleasePeriods() > 0;

        // 释放期数（releasePeriods）为高优先级：
        // - 配置了期数：按期数平均分摊 totalQty，每期释放；不再“先把购买桶数一次性释放”。
        // - 未配置期数：保持原逻辑（购买量立即可用，赠送量冻结并按计划释放）。
        int initAvailableQty = useReleasePeriods ? 0 : buyQty;
        int initFrozenQty = useReleasePeriods ? totalQty : giftQty;
        int initReleasedQty = useReleasePeriods ? 0 : buyQty;

        GltUserTicket userTicket = new GltUserTicket();
        userTicket.setTemplateId(template.getId());
        userTicket.setGoodsId(og.getGoodsId());
        userTicket.setOrderId(order.getOrderId());
        userTicket.setOrderNo(order.getOrderNo());
        userTicket.setOrderGoodsId(og.getId());
        userTicket.setTotalQty(totalQty);
        userTicket.setAvailableQty(initAvailableQty);
        userTicket.setFrozenQty(initFrozenQty);
        userTicket.setUsedQty(0);
        userTicket.setReleasedQty(initReleasedQty);
        userTicket.setOrderGoodsQty(og.getTotalNum());
        userTicket.setUserId(order.getUserId());
        userTicket.setSortNumber(0);
        userTicket.setComments("订单发放套票");
        userTicket.setStatus(0);
        userTicket.setDeleted(0);
        userTicket.setTenantId(tenantId);
        userTicket.setCreateTime(now);
        userTicket.setUpdateTime(now);

        gltUserTicketService.save(userTicket);

        // 生成释放计划：
        // - 配置 releasePeriods：按 totalQty 生成每期释放量（periods 优先）
        // - 未配置 releasePeriods：按 giftQty 生成每期释放量
        LocalDateTime baseTime = order.getPayTime() != null ? order.getPayTime() : order.getCreateTime();
        if (baseTime == null) {
            baseTime = now;
        }
        int planQty = useReleasePeriods ? totalQty : giftQty;
        List<GltUserTicketRelease> releases = buildReleasePlan(template, userTicket, baseTime, planQty, now);

        // 若启用了 releasePeriods 且首期释放时机为“支付成功当刻”，则将首期释放量直接计入可用，
        // 避免用户刚购买后短时间内无可用水票；后续期数仍由自动释放任务按 release_time 释放。
//        if (useReleasePeriods && !releases.isEmpty() && !Objects.equals(template.getFirstReleaseMode(), 1)) {
        if (!releases.isEmpty() && !Objects.equals(template.getFirstReleaseMode(), 1)) {
            GltUserTicketRelease first = releases.get(0);
            Integer firstQtyObj = first.getReleaseQty();
            LocalDateTime firstTime = first.getReleaseTime();
            int firstQty = firstQtyObj != null ? firstQtyObj : 0;
//            if (firstQty > 0 && (firstTime == null || !firstTime.isAfter(now))) {
            if (firstQty > 0) {
                first.setStatus(1);
                first.setUpdateTime(now);

                userTicket.setAvailableQty((userTicket.getAvailableQty() != null ? userTicket.getAvailableQty() : 0) + firstQty);
                userTicket.setFrozenQty((userTicket.getFrozenQty() != null ? userTicket.getFrozenQty() : 0) - firstQty);
                userTicket.setReleasedQty((userTicket.getReleasedQty() != null ? userTicket.getReleasedQty() : 0) + firstQty);
                userTicket.setUpdateTime(now);
                if (!gltUserTicketService.updateById(userTicket)) {
                    throw new IllegalStateException("首期释放：更新用户水票失败 userTicketId=" + userTicket.getId());
                }
            }
        }
        if (!releases.isEmpty()) {
            gltUserTicketReleaseService.saveBatch(releases);
        }

        // 发放流水
        GltUserTicketLog issueLog = new GltUserTicketLog();
        issueLog.setUserTicketId(userTicket.getId());
        issueLog.setChangeType(CHANGE_TYPE_ISSUE);
        issueLog.setChangeAvailable(userTicket.getAvailableQty() != null ? userTicket.getAvailableQty() : 0);
        issueLog.setChangeFrozen(userTicket.getFrozenQty() != null ? userTicket.getFrozenQty() : 0);
        issueLog.setChangeUsed(0);
        issueLog.setAvailableAfter(userTicket.getAvailableQty() != null ? userTicket.getAvailableQty() : 0);
        issueLog.setFrozenAfter(userTicket.getFrozenQty() != null ? userTicket.getFrozenQty() : 0);
        issueLog.setUsedAfter(0);
        issueLog.setOrderId(order.getOrderId());
        issueLog.setOrderNo(order.getOrderNo());
        issueLog.setUserId(order.getUserId());
        issueLog.setSortNumber(0);
        issueLog.setComments("套票发放");
        issueLog.setStatus(0);
        issueLog.setDeleted(0);
        issueLog.setTenantId(tenantId);
        issueLog.setCreateTime(now);
        issueLog.setUpdateTime(now);
        gltUserTicketLogService.save(issueLog);

        // 按整改需求：水票购买（囤券预付费）与水票核销（下单履约）应为两次独立用户动作；
        // 因此模板 startSendQty 不再在“发放”阶段自动核销/自动生成送水订单。
        Integer startSendQtyObj = template.getStartSendQty();
        int startSendQty = startSendQtyObj != null ? startSendQtyObj : 0;
        if (startSendQty > 0) {
            log.info("套票模板配置了 startSendQty，但不再自动送水/自动核销 - tenantId={}, orderNo={}, templateId={}, userTicketId={}, startSendQty={}",
                tenantId, order.getOrderNo(), template.getId(), userTicket.getId(), startSendQty);
        }

        log.info("套票发放成功 - tenantId={}, orderNo={}, orderGoodsId={}, templateId={}, userTicketId={}, orderGoodsQty={}, buyQty={}, giftQty={}, startSendQty={}, totalQty={}",
            tenantId, order.getOrderNo(), og.getId(), template.getId(), userTicket.getId(), og.getTotalNum(), buyQty, giftQty, startSendQty, totalQty);

        return IssueOutcome.ISSUED;
    }

    private List<GltUserTicketRelease> buildReleasePlan(GltTicketTemplate template,
                                                       GltUserTicket userTicket,
                                                       LocalDateTime baseTime,
                                                       int totalQty,
                                                       LocalDateTime now) {
        List<GltUserTicketRelease> list = new ArrayList<>();

        if (totalQty <= 0) {
            return list;
        }

        // 首期释放时间
        LocalDateTime firstReleaseTime;
        LocalDateTime referenceTime;
        if (Objects.equals(template.getFirstReleaseMode(), 1)) {
            firstReleaseTime = nextMonthSameDay(baseTime);
            referenceTime = firstReleaseTime.withDayOfMonth(1).toLocalDate().atStartOfDay();
        } else {
            firstReleaseTime = baseTime;
            referenceTime = firstReleaseTime.withDayOfMonth(1).toLocalDate().atStartOfDay();
        }

        // 每期释放数量计算
        Integer releasePeriods = template.getReleasePeriods();
        if (releasePeriods != null && releasePeriods > 0) {
            int base = totalQty / releasePeriods;
            int remainder = totalQty % releasePeriods;
            // periodNo 从 0 开始：第0期、第1期……（更贴近任务执行计数）
            for (int i = 0; i < releasePeriods; i++) {
                int qty = base + (i < remainder ? 1 : 0);
                if (qty <= 0) {
                    continue;
                }
                if(i == 0){
                    list.add(buildRelease(userTicket, i, qty, firstReleaseTime, now));
                }else {
                    list.add(buildRelease(userTicket, i, qty, referenceTime.plusMonths(i), now));
                }
            }
            return list;
        }

        int monthlyReleaseQty = template.getMonthlyReleaseQty() != null && template.getMonthlyReleaseQty() > 0
            ? template.getMonthlyReleaseQty()
            : 10;
        int periods = (totalQty + monthlyReleaseQty - 1) / monthlyReleaseQty;
        int remaining = totalQty;
        // periodNo 从 0 开始：第0期、第1期……
        for (int i = 0; i < periods; i++) {
            int qty = Math.min(monthlyReleaseQty, remaining);
            if (qty <= 0) {
                break;
            }
            remaining -= qty;
            if(i == 0){
                list.add(buildRelease(userTicket, i, qty, firstReleaseTime, now));
            }else {
                list.add(buildRelease(userTicket, i, qty, referenceTime.plusMonths(i), now));
            }
        }

        return list;
    }

    private GltUserTicketRelease buildRelease(GltUserTicket userTicket,
                                             int periodNo,
                                             int releaseQty,
                                             LocalDateTime releaseTime,
                                             LocalDateTime now) {
        GltUserTicketRelease r = new GltUserTicketRelease();
        r.setUserTicketId(userTicket.getId() != null ? userTicket.getId() : null);
        r.setUserId(userTicket.getUserId());
        r.setPeriodNo(periodNo);
        r.setReleaseQty(releaseQty);
        r.setReleaseTime(releaseTime);
        r.setStatus(0);
        r.setDeleted(0);
        r.setTenantId(userTicket.getTenantId());
        r.setCreateTime(now);
        r.setUpdateTime(now);
        return r;
    }

    private static LocalDateTime nextMonthSameDay(LocalDateTime baseTime) {
        LocalDate baseDate = baseTime.toLocalDate();
        LocalTime time = baseTime.toLocalTime();

        LocalDate nextMonth = baseDate.plusMonths(1);
        int day = Math.min(baseDate.getDayOfMonth(), nextMonth.lengthOfMonth());
        LocalDate adjusted = nextMonth.withDayOfMonth(day);
        return LocalDateTime.of(adjusted, time);
    }

}
