package com.jeesharp.business.modules.coupon.application;

import cn.hutool.core.util.StrUtil;
import com.jeesharp.business.modules.coupon.application.support.CouponDiscountCalculator;
import com.jeesharp.business.modules.coupon.application.support.CouponMatchSupport;
import com.jeesharp.business.modules.coupon.dto.CouponCheckoutContextDTO;
import com.jeesharp.business.modules.coupon.dto.CouponFreezeCommandDTO;
import com.jeesharp.business.modules.coupon.entity.CouponTemplate;
import com.jeesharp.business.modules.coupon.entity.MemberCoupon;
import com.jeesharp.business.modules.coupon.enums.CouponCheckoutUnusableReasonEnum;
import com.jeesharp.business.modules.coupon.enums.CouponDiscountTypeEnum;
import com.jeesharp.business.modules.coupon.enums.MemberCouponUseStatusEnum;
import com.jeesharp.business.modules.coupon.service.CouponTemplateService;
import com.jeesharp.business.modules.coupon.service.MemberCouponService;
import com.jeesharp.business.modules.coupon.vo.CouponCheckoutListVO;
import com.jeesharp.business.modules.coupon.vo.CouponCheckoutRowVO;
import com.jeesharp.business.modules.coupon.vo.CouponOrderDiscountVO;
import com.jeesharp.business.modules.coupon.vo.MemberCouponQueryVO;
import com.jeesharp.business.modules.member.entity.Member;
import com.jeesharp.business.modules.member.service.MemberService;
import com.jeesharp.framework.exception.BizErrorException;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 优惠券结账门面：多业务（商城/酒店/门票等）统一的试算、冻结、核销与释放。
 *
 * <p><b>券在订单中的生命周期</b></p>
 * <pre>
 * 未使用(UNUSED)
 *   → 下单选券：freezeForOrder → 冻结(FROZEN)，绑定 freezeOrderId
 *   → 支付成功：confirmOrderPaid → 已使用(USED)
 *   → 取消/超时：releaseFrozen → 回到未使用(UNUSED)
 *   → 退款：returnOnRefund → 回到未使用(USED→UNUSED，标记 isReturned)
 * </pre>
 *
 * <p>商城调用链：{@code MallOrderApplicationService} → {@code CouponCheckoutApi} → 本类。</p>
 *
 * @author jeesharp
 * @since 2026-06-05
 */
@Service
@AllArgsConstructor
@Transactional(readOnly = true)
public class CouponCheckoutApplicationService {

    private final MemberCouponService memberCouponService;
    private final CouponTemplateService couponTemplateService;
    private final MemberService memberService;
    private final CouponMatchSupport couponMatchSupport;

    /**
     * 结账页：列出可用与不可用券。
     *
     * <p>过滤顺序：有效期 → 场景/商品范围/会员等级（{@link CouponMatchSupport}）→ 满减门槛 → 预估抵扣额。</p>
     */
    public CouponCheckoutListVO listForCheckout(CouponCheckoutContextDTO context) {
        validateContext(context);
        enrichMemberLevel(context);
        memberCouponService.markExpiredIfPastEnd(context.getMemberId());

        MemberCouponQueryVO q = new MemberCouponQueryVO();
        q.setMemberId(context.getMemberId());
        q.setStatus(MemberCouponUseStatusEnum.UNUSED.getValue());
        List<MemberCoupon> coupons = memberCouponService.findList(q);

        CouponCheckoutListVO result = new CouponCheckoutListVO();
        LocalDateTime now = LocalDateTime.now();
        for (MemberCoupon coupon : coupons) {
            CouponCheckoutRowVO row = toCheckoutRow(coupon);
            CouponTemplate template = loadTemplate(coupon.getTemplateId());
            if (template != null) {
                if (StrUtil.isNotBlank(template.getTenantCode())) {
                    row.setTemplateTenantCode(template.getTenantCode().trim());
                } else if (StrUtil.isNotBlank(template.getTenantId())) {
                    row.setTemplateTenantCode(template.getTenantId().trim());
                }
            }
            if (StrUtil.isNotBlank(coupon.getSceneCode())) {
                row.setSceneCode(coupon.getSceneCode());
            } else if (template != null) {
                row.setSceneCode(template.getSceneCode());
            }

            String timeReason = unusableByValidity(coupon, now);
            if (timeReason != null) {
                applyUnusable(row, timeReason, mapTimeReasonCode(timeReason));
                result.getUnusableCoupons().add(row);
                continue;
            }

            CouponCheckoutUnusableReasonEnum ruleReason =
                    couponMatchSupport.matchMemberCouponRule(coupon, template, context);
            if (ruleReason != null) {
                applyUnusable(row, ruleReason.getDefaultMessage(), ruleReason.getCode());
                result.getUnusableCoupons().add(row);
                continue;
            }

            String amountReason = unusableByMinAmount(coupon, context.getOrderAmount());
            if (amountReason != null) {
                applyUnusable(row, amountReason, CouponCheckoutUnusableReasonEnum.BELOW_MIN_AMOUNT.getCode());
                result.getUnusableCoupons().add(row);
                continue;
            }

            row.setEstimatedDiscount(CouponDiscountCalculator.compute(coupon, context.getOrderAmount()));
            result.getUsableCoupons().add(row);
        }
        return result;
    }

    /**
     * 兼容旧接口：仅金额试算，不按场景/范围过滤。
     */
    public Map<String, Object> listForCheckoutLegacyMap(String memberId, BigDecimal orderAmount) {
        CouponCheckoutContextDTO context = new CouponCheckoutContextDTO();
        context.setMemberId(memberId);
        context.setOrderAmount(orderAmount);
        CouponCheckoutListVO vo = listForCheckout(context);
        Map<String, Object> data = new HashMap<>();
        data.put("usableCoupons", vo.getUsableCoupons().stream().map(this::legacyRowFromVo).toList());
        data.put("unusableCoupons", vo.getUnusableCoupons().stream().map(this::legacyRowFromVo).toList());
        return data;
    }

    /**
     * 单券试算抵扣金额；未选券返回 0。
     */
    public BigDecimal resolveDiscount(CouponCheckoutContextDTO context, String memberCouponId) {
        if (StrUtil.isBlank(memberCouponId)) {
            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        }
        MemberCoupon coupon = loadUsableMemberCouponOrThrow(context, memberCouponId);
        return CouponDiscountCalculator.compute(coupon, context.getOrderAmount());
    }

    /**
     * 下单成功：冻结会员券，待支付完成后核销。
     */
    @Transactional(readOnly = false)
    public void freezeForOrder(CouponFreezeCommandDTO command) {
        if (command == null || StrUtil.isBlank(command.getMemberCouponId())) {
            return;
        }
        CouponCheckoutContextDTO context = command.getCheckoutContext();
        if (context == null) {
            throw new BizErrorException("结账上下文不能为空");
        }
        MemberCoupon coupon = loadUsableMemberCouponOrThrow(context, command.getMemberCouponId());
        BigDecimal actualDiscount = CouponDiscountCalculator.compute(coupon, context.getOrderAmount());
        if (command.getExpectedDiscount() != null
                && actualDiscount.compareTo(command.getExpectedDiscount().setScale(2, RoundingMode.HALF_UP)) != 0) {
            throw new BizErrorException("优惠券抵扣金额已变化，请重新选择");
        }
        coupon.setStatus(MemberCouponUseStatusEnum.FROZEN.getValue());
        coupon.setFreezeDate(LocalDateTime.now());
        coupon.setFreezeReason(StrUtil.blankToDefault(command.getFreezeReason(), "订单待支付"));
        coupon.setFreezeOrderId(command.getOrderId());
        coupon.setUseOrderId(command.getOrderId());
        coupon.setUseOrderNo(command.getOrderNo());
        coupon.setOrderAmount(context.getOrderAmount());
        coupon.setUseAmount(actualDiscount);
        memberCouponService.update(coupon);
    }

    /**
     * 支付成功：将冻结券置为已使用。
     */
    @Transactional(readOnly = false)
    public void confirmOrderPaid(String orderId) {
        if (StrUtil.isBlank(orderId)) {
            return;
        }
        MemberCoupon coupon = memberCouponService.findByFreezeOrderId(orderId);
        if (coupon == null || !MemberCouponUseStatusEnum.FROZEN.getValue().equals(coupon.getStatus())) {
            return;
        }
        coupon.setStatus(MemberCouponUseStatusEnum.USED.getValue());
        coupon.setUseDate(LocalDateTime.now());
        memberCouponService.update(coupon);
        if (StrUtil.isNotBlank(coupon.getTemplateId())) {
            couponTemplateService.increaseUsedCount(coupon.getTemplateId());
        }
    }

    /**
     * 订单退款成功：将已使用券退回为未使用（幂等，已退回则跳过）。
     */
    @Transactional(readOnly = false)
    public void returnOnRefund(String orderId, String reason) {
        if (StrUtil.isBlank(orderId)) {
            return;
        }
        MemberCoupon coupon = memberCouponService.findUsedByOrderId(orderId.trim());
        if (coupon == null) {
            return;
        }
        if (coupon.getIsReturned() != null && coupon.getIsReturned() == 1) {
            return;
        }
        if (!MemberCouponUseStatusEnum.USED.getValue().equals(coupon.getStatus())) {
            return;
        }
        coupon.setStatus(MemberCouponUseStatusEnum.UNUSED.getValue());
        coupon.setIsReturned(1);
        coupon.setReturnDate(LocalDateTime.now());
        coupon.setReturnReason(StrUtil.blankToDefault(reason, "订单退款"));
        coupon.setUseDate(null);
        coupon.setUseOrderId(null);
        coupon.setUseOrderNo(null);
        coupon.setUseAmount(null);
        coupon.setOrderAmount(null);
        coupon.setFreezeDate(null);
        coupon.setFreezeReason(null);
        coupon.setFreezeOrderId(null);
        memberCouponService.update(coupon);
        if (StrUtil.isNotBlank(coupon.getTemplateId())) {
            couponTemplateService.decreaseUsedCountIfPositive(coupon.getTemplateId());
        }
    }

    /**
     * 查询订单已冻结的优惠券抵扣额（待支付预下单金额调减用）。
     */
    public BigDecimal getFrozenDiscountAmount(String orderId) {
        if (StrUtil.isBlank(orderId)) {
            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        }
        MemberCoupon coupon = memberCouponService.findByFreezeOrderId(orderId.trim());
        if (coupon == null || !MemberCouponUseStatusEnum.FROZEN.getValue().equals(coupon.getStatus())) {
            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        }
        BigDecimal useAmount = coupon.getUseAmount();
        if (useAmount == null || useAmount.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        }
        return useAmount.setScale(2, RoundingMode.HALF_UP);
    }

    /**
     * 订单列表/详情：展示用优惠券抵扣（冻结待付或已使用且未退回）。
     */
    public CouponOrderDiscountVO resolveOrderCouponDisplay(String orderId) {
        if (StrUtil.isBlank(orderId)) {
            return null;
        }
        MemberCoupon coupon = memberCouponService.findByFreezeOrderId(orderId.trim());
        if (coupon == null) {
            coupon = memberCouponService.findUsedByOrderId(orderId.trim());
        }
        if (coupon == null) {
            return null;
        }
        Integer status = coupon.getStatus();
        if (!MemberCouponUseStatusEnum.FROZEN.getValue().equals(status)
                && !MemberCouponUseStatusEnum.USED.getValue().equals(status)) {
            return null;
        }
        BigDecimal useAmount = coupon.getUseAmount();
        if (useAmount == null || useAmount.compareTo(BigDecimal.ZERO) <= 0) {
            return null;
        }
        CouponOrderDiscountVO vo = new CouponOrderDiscountVO();
        vo.setCouponName(coupon.getCouponName());
        vo.setCouponDiscountAmount(useAmount.setScale(2, RoundingMode.HALF_UP));
        return vo;
    }

    /**
     * 解析会员券模板租户编码，用于区分平台券与商户券。
     */
    public String resolveTemplateTenantCode(String memberCouponId) {
        if (StrUtil.isBlank(memberCouponId)) {
            return null;
        }
        MemberCoupon coupon = memberCouponService.get(memberCouponId.trim());
        if (coupon == null) {
            return null;
        }
        CouponTemplate template = loadTemplate(coupon.getTemplateId());
        if (template == null) {
            return null;
        }
        if (StrUtil.isNotBlank(template.getTenantCode())) {
            return template.getTenantCode().trim();
        }
        if (StrUtil.isNotBlank(template.getTenantId())) {
            return template.getTenantId().trim();
        }
        return null;
    }

    /**
     * 商城合并支付：核销 batch 平台券与各子单商户券（幂等）。
     */
    @Transactional(readOnly = false)
    public void confirmAllFrozenForMallBatch(String batchId, List<String> subOrderIds) {
        if (StrUtil.isNotBlank(batchId)) {
            confirmOrderPaid(batchId.trim());
        }
        if (subOrderIds == null) {
            return;
        }
        for (String orderId : subOrderIds) {
            if (StrUtil.isNotBlank(orderId)) {
                confirmOrderPaid(orderId.trim());
            }
        }
    }

    /**
     * 商城合并支付：释放 batch 平台券与各子单商户券冻结（幂等）。
     */
    @Transactional(readOnly = false)
    public void releaseAllFrozenForMallBatch(String batchId, List<String> subOrderIds, String reason) {
        if (StrUtil.isNotBlank(batchId)) {
            releaseFrozen(batchId.trim(), reason);
        }
        if (subOrderIds == null) {
            return;
        }
        for (String orderId : subOrderIds) {
            if (StrUtil.isNotBlank(orderId)) {
                releaseFrozen(orderId.trim(), reason);
            }
        }
    }

    /**
     * 订单取消/超时：释放冻结券为未使用。
     */
    @Transactional(readOnly = false)
    public void releaseFrozen(String orderId, String reason) {
        if (StrUtil.isBlank(orderId)) {
            return;
        }
        MemberCoupon coupon = memberCouponService.findByFreezeOrderId(orderId);
        if (coupon == null || !MemberCouponUseStatusEnum.FROZEN.getValue().equals(coupon.getStatus())) {
            return;
        }
        coupon.setStatus(MemberCouponUseStatusEnum.UNUSED.getValue());
        coupon.setFreezeDate(null);
        coupon.setFreezeReason(StrUtil.blankToDefault(reason, "订单已取消"));
        coupon.setFreezeOrderId(null);
        coupon.setUseOrderId(null);
        coupon.setUseOrderNo(null);
        coupon.setUseAmount(null);
        coupon.setOrderAmount(null);
        memberCouponService.update(coupon);
    }

    private MemberCoupon loadUsableMemberCouponOrThrow(CouponCheckoutContextDTO context, String memberCouponId) {
        validateContext(context);
        enrichMemberLevel(context);
        memberCouponService.markExpiredIfPastEnd(context.getMemberId());
        MemberCoupon coupon = memberCouponService.get(memberCouponId);
        if (coupon == null || !context.getMemberId().equals(coupon.getMemberId())) {
            throw new BizErrorException("优惠券不存在");
        }
        if (!MemberCouponUseStatusEnum.UNUSED.getValue().equals(coupon.getStatus())) {
            throw new BizErrorException("优惠券不可用");
        }
        LocalDateTime now = LocalDateTime.now();
        String timeReason = unusableByValidity(coupon, now);
        if (timeReason != null) {
            throw new BizErrorException(timeReason);
        }
        CouponTemplate template = loadTemplate(coupon.getTemplateId());
        CouponCheckoutUnusableReasonEnum ruleReason =
                couponMatchSupport.matchMemberCouponRule(coupon, template, context);
        if (ruleReason != null) {
            throw new BizErrorException(ruleReason.getDefaultMessage());
        }
        String amountReason = unusableByMinAmount(coupon, context.getOrderAmount());
        if (amountReason != null) {
            throw new BizErrorException(amountReason);
        }
        return coupon;
    }

    private void validateContext(CouponCheckoutContextDTO context) {
        if (context == null || StrUtil.isBlank(context.getMemberId())) {
            throw new BizErrorException("会员信息不能为空");
        }
        if (context.getOrderAmount() == null || context.getOrderAmount().compareTo(BigDecimal.ZERO) < 0) {
            throw new BizErrorException("订单金额不合法");
        }
    }

    private void enrichMemberLevel(CouponCheckoutContextDTO context) {
        if (StrUtil.isNotBlank(context.getMemberLevelCode())) {
            return;
        }
        Member member = memberService.get(context.getMemberId());
        if (member != null && StrUtil.isNotBlank(member.getLevelCode())) {
            context.setMemberLevelCode(member.getLevelCode().trim());
        }
    }

    private CouponTemplate loadTemplate(String templateId) {
        if (StrUtil.isBlank(templateId)) {
            return null;
        }
        CouponTemplate template = couponTemplateService.get(templateId);
        if (template == null || !"0".equals(template.getDelFlag())) {
            return null;
        }
        return template;
    }

    private CouponCheckoutRowVO toCheckoutRow(MemberCoupon coupon) {
        CouponCheckoutRowVO row = new CouponCheckoutRowVO();
        row.setMemberCouponId(coupon.getId());
        row.setCouponName(coupon.getCouponName());
        row.setDiscountType(coupon.getDiscountType());
        row.setDiscountTypeName(CouponDiscountTypeEnum.getDescByValue(coupon.getDiscountType()));
        row.setDiscountValue(coupon.getDiscountValue());
        row.setMinConsumeAmount(coupon.getMinConsumeAmount());
        row.setValidStartDate(coupon.getValidStartDate());
        row.setValidEndDate(coupon.getValidEndDate());
        return row;
    }

    private void applyUnusable(CouponCheckoutRowVO row, String message, String code) {
        row.setReason(message);
        row.setReasonCode(code);
    }

    private String unusableByValidity(MemberCoupon coupon, LocalDateTime now) {
        if (coupon.getValidEndDate() != null && coupon.getValidEndDate().isBefore(now)) {
            return CouponCheckoutUnusableReasonEnum.EXPIRED.getDefaultMessage();
        }
        if (coupon.getValidStartDate() != null && coupon.getValidStartDate().isAfter(now)) {
            return CouponCheckoutUnusableReasonEnum.NOT_STARTED.getDefaultMessage();
        }
        return null;
    }

    private String mapTimeReasonCode(String message) {
        if (CouponCheckoutUnusableReasonEnum.EXPIRED.getDefaultMessage().equals(message)) {
            return CouponCheckoutUnusableReasonEnum.EXPIRED.getCode();
        }
        if (CouponCheckoutUnusableReasonEnum.NOT_STARTED.getDefaultMessage().equals(message)) {
            return CouponCheckoutUnusableReasonEnum.NOT_STARTED.getCode();
        }
        return CouponCheckoutUnusableReasonEnum.STATUS_INVALID.getCode();
    }

    private String unusableByMinAmount(MemberCoupon coupon, BigDecimal orderAmount) {
        BigDecimal min = coupon.getMinConsumeAmount() != null ? coupon.getMinConsumeAmount() : BigDecimal.ZERO;
        if (orderAmount.compareTo(min) < 0) {
            return CouponCheckoutUnusableReasonEnum.formatBelowMinAmount(min);
        }
        return null;
    }

    private Map<String, Object> legacyRowFromVo(CouponCheckoutRowVO row) {
        Map<String, Object> map = new HashMap<>();
        map.put("couponId", row.getMemberCouponId());
        map.put("memberCouponId", row.getMemberCouponId());
        map.put("couponName", row.getCouponName());
        map.put("discountType", row.getDiscountType());
        map.put("discountTypeName", row.getDiscountTypeName());
        map.put("discountValue", row.getDiscountValue());
        map.put("minConsumeAmount", row.getMinConsumeAmount());
        if (row.getValidStartDate() != null) {
            map.put("validStartDate", row.getValidStartDate().toLocalDate().toString());
        }
        if (row.getValidEndDate() != null) {
            map.put("validEndDate", row.getValidEndDate().toLocalDate().toString());
        }
        if (row.getEstimatedDiscount() != null) {
            map.put("estimatedDiscount", row.getEstimatedDiscount());
        }
        if (row.getReason() != null) {
            map.put("reason", row.getReason());
        }
        if (row.getReasonCode() != null) {
            map.put("reasonCode", row.getReasonCode());
        }
        if (row.getSceneCode() != null) {
            map.put("sceneCode", row.getSceneCode());
        }
        if (row.getTemplateTenantCode() != null) {
            map.put("templateTenantCode", row.getTemplateTenantCode());
        }
        return map;
    }
}
