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

import cn.hutool.core.util.StrUtil;
import com.jeesharp.business.modules.trade.dto.TradeOrderCreateDTO;
import com.jeesharp.business.modules.trade.entity.TradeOrder;
import com.jeesharp.business.modules.trade.entity.TradeOrderItem;
import com.jeesharp.business.modules.trade.enums.TradeBizTypeEnum;
import com.jeesharp.business.modules.trade.enums.TradeOrderStatusEnum;
import com.jeesharp.business.modules.trade.application.support.TradeOrderCouponDisplayEnricher;
import com.jeesharp.business.modules.trade.service.TradeOrderItemService;
import com.jeesharp.business.modules.trade.service.TradeOrderService;
import com.jeesharp.business.modules.trade.vo.TradeOrderInfoVO;
import com.jeesharp.business.modules.trade.vo.TradeOrderQueryVO;
import com.jeesharp.framework.pojo.Page;
import com.jeesharp.framework.pojo.query.PageRespVO;
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.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 统一交易订单中心（{@code biz_trade_order}）：各垂直业务「双写」后的聚合展示层。
 *
 * <p><b>与各业务的关系</b></p>
 * <pre>
 * 商城/酒店/门票/美食/研学/年卡/休闲 等在各自 Application 下单或支付成功后，
 * 通过 TradeOrderFacadeApi 写入或更新本表（vertical_order_id 指向垂直单主键）。
 *
 * 小程序「我的订单」TradeOrderMobileController → listUnifiedOrdersForMiniProgram
 * 将多业态订单卡片化展示（bizType + merchants 摘要 + 优惠券展示补全）。
 * </pre>
 *
 * <p>本类不承载垂直业务规则（库存、核销、冻券等），仅维护统一单状态与 C 端列表组装。</p>
 *
 * @author jeesharp
 * @since 2026-05-08
 */
@Service
@AllArgsConstructor
@Transactional(readOnly = true)
public class TradeOrderApplicationService {

    private static final DateTimeFormatter MINI_TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    private final TradeOrderService tradeOrderService;
    private final TradeOrderItemService tradeOrderItemService;
    private final TradeOrderCouponDisplayEnricher tradeOrderCouponDisplayEnricher;

    /**
     * 创建待支付统一订单及可选明细行。
     */
    @Transactional(readOnly = false)
    public TradeOrderInfoVO createPendingPayOrder(TradeOrderCreateDTO dto) {
        TradeOrder order = tradeOrderService.createPendingPayOrder(dto);
        return toInfoVO(order);
    }

    /**
     * 支付成功：待支付 → 已支付，并记录支付单号与渠道。
     */
    @Transactional(readOnly = false)
    public void markPaid(String orderId, String payOrderId, String payChannel) {
        tradeOrderService.markPaid(orderId, payOrderId, payChannel);
    }

    /**
     * 待支付取消。
     */
    @Transactional(readOnly = false)
    public void cancelPending(String orderId) {
        tradeOrderService.cancelPending(orderId);
    }

    /**
     * 预约/订单取消时同步统一单为已取消。
     */
    @Transactional(readOnly = false)
    public void markCancelled(String orderId) {
        if (StrUtil.isBlank(orderId)) {
            return;
        }
        tradeOrderService.markCancelled(orderId);
    }

    /**
     * 到店核销完成后同步统一单为已关闭（小程序列表展示为已完成）。
     */
    @Transactional(readOnly = false)
    public void markClosed(String orderId) {
        if (StrUtil.isBlank(orderId)) {
            return;
        }
        tradeOrderService.markClosed(orderId);
    }

    /**
     * 商城快递发货：已支付 → 履约中。
     */
    @Transactional(readOnly = false)
    public void markFulfilling(String orderId) {
        if (StrUtil.isBlank(orderId)) {
            return;
        }
        tradeOrderService.markFulfilling(orderId);
    }

    /**
     * 商城确认收货/自提核销：已支付或履约中 → 已完成。
     */
    @Transactional(readOnly = false)
    public void markFulfilled(String orderId) {
        if (StrUtil.isBlank(orderId)) {
            return;
        }
        tradeOrderService.markFulfilled(orderId);
    }

    public TradeOrder get(String id) {
        return tradeOrderService.get(id);
    }

    public Page<TradeOrder> findPage(Page<TradeOrder> page, TradeOrderQueryVO query) {
        return tradeOrderService.findPage(page, query);
    }

    private static final long MINI_PROGRAM_DEFAULT_PAGE_SIZE = 20L;

    /**
     * 小程序「我的」统一订单中心列表：分页查询 {@code biz_trade_order}，按行拼装 C 端订单卡片 Map（含 merchants 摘要）。
     *
     * @param memberId 当前会员主键
     * @param query    分页与可选筛选（{@code memberId} 由本方法强制覆盖为当前登录人）
     * @return 分页结果（含 {@code total}，默认每页 20 条）
     */
    public PageRespVO<Map<String, Object>> listUnifiedOrdersForMiniProgram(String memberId, TradeOrderQueryVO query) {
        TradeOrderQueryVO q = query != null ? query : new TradeOrderQueryVO();
        long pageNo = q.getPageNo() == null || q.getPageNo() < 1 ? 1L : q.getPageNo();
        long pageSize = q.getPageSize() == null || q.getPageSize() < 1 ? MINI_PROGRAM_DEFAULT_PAGE_SIZE : q.getPageSize();
        q.setPageNo(pageNo);
        q.setPageSize(pageSize);
        q.setMemberId(memberId);
        if (StrUtil.isNotBlank(q.getBizType())) {
            q.setBizType(fromClientBizType(q.getBizType()));
        }
        q.closeDataScope();
        Page<TradeOrder> page = new Page<>(q);
        Page<TradeOrder> result = tradeOrderService.findPage(page, q);
        List<String> orderIds = result.getRecords().stream().map(TradeOrder::getId).filter(Objects::nonNull).toList();
        Map<String, List<TradeOrderItem>> itemsByOrderId = new LinkedHashMap<>();
        if (!orderIds.isEmpty()) {
            for (TradeOrderItem it : tradeOrderItemService.findByTradeOrderIds(orderIds)) {
                if (StrUtil.isBlank(it.getOrderId())) {
                    continue;
                }
                itemsByOrderId.computeIfAbsent(it.getOrderId(), k -> new ArrayList<>()).add(it);
            }
        }
        List<Map<String, Object>> rows = new ArrayList<>();
        for (TradeOrder o : result.getRecords()) {
            rows.add(toMiniProgramOrderRow(o, itemsByOrderId.getOrDefault(o.getId(), List.of())));
        }
        tradeOrderCouponDisplayEnricher.enrichRows(rows);
        PageRespVO<Map<String, Object>> pageResp = new PageRespVO<>();
        pageResp.setRecords(rows);
        pageResp.setTotal(result.getTotal());
        pageResp.setPageNo(result.getPageNo());
        pageResp.setPageSize(result.getPageSize());
        return pageResp;
    }

    /**
     * 小程序统一订单详情：支持主键 {@code biz_trade_order.id} 或列表导航用的 {@code verticalOrderId}。
     *
     * @return 与列表行结构一致的 Map；非当前会员或不存在时返回 {@code null}
     */
    public Map<String, Object> getUnifiedOrderDetailForMiniProgram(String memberId, String id) {
        if (StrUtil.isBlank(memberId) || StrUtil.isBlank(id)) {
            return null;
        }
        TradeOrderQueryVO q = new TradeOrderQueryVO();
        q.setId(id.trim());
        q.setMemberId(memberId);
        q.closeDataScope();
        q.setPageNo(1L);
        q.setPageSize(1L);
        Page<TradeOrder> page = tradeOrderService.findPage(new Page<>(1L, 1L), q);
        TradeOrder order = page.getRecords().isEmpty() ? null : page.getRecords().get(0);
        if (order == null) {
            q = new TradeOrderQueryVO();
            q.setMemberId(memberId);
            q.setVerticalOrderId(id.trim());
            q.closeDataScope();
            q.setPageNo(1L);
            q.setPageSize(1L);
            page = tradeOrderService.findPage(new Page<>(1L, 1L), q);
            order = page.getRecords().isEmpty() ? null : page.getRecords().get(0);
        }
        if (order == null || !memberId.equals(order.getMemberId())) {
            return null;
        }
        List<TradeOrderItem> items = tradeOrderItemService.findByTradeOrderIds(List.of(order.getId()));
        Map<String, Object> row = toMiniProgramOrderRow(order, items);
        tradeOrderCouponDisplayEnricher.enrichRow(row);
        return row;
    }

    private Map<String, Object> toMiniProgramOrderRow(TradeOrder o, List<TradeOrderItem> items) {
        Map<String, Object> m = new LinkedHashMap<>();
        String navigateId = StrUtil.blankToDefault(o.getVerticalOrderId(), o.getId());
        m.put("id", navigateId);
        m.put("unifiedOrderId", o.getId());
        m.put("orderNo", StrUtil.blankToDefault(o.getOrderNo(), o.getId()));
        m.put("bizType", toClientBizType(o.getBizType()));
        m.put("status", toClientOrderStatus(o.getStatus()));
        m.put("merchants", buildMerchantsBlock(o, items));
        m.put("totalAmount", formatMoney(o.getPayAmount() != null ? o.getPayAmount() : o.getTotalAmount()));
        m.put("createTime", o.getCreateDate() != null ? MINI_TIME_FMT.format(o.getCreateDate()) : "");
        Map<String, String> addr = new LinkedHashMap<>();
        addr.put("name", "");
        addr.put("phone", "");
        addr.put("detail", StrUtil.blankToDefault(o.getTitle(), ""));
        m.put("address", addr);
        boolean mall = TradeBizTypeEnum.MALL.getValue().equals(o.getBizType());
        m.put("hasReviewed", mall ? Boolean.FALSE : Boolean.TRUE);
        return m;
    }

    private static String toClientBizType(String bizType) {
        TradeBizTypeEnum e = TradeBizTypeEnum.fromValue(bizType);
        if (e == null) {
            return "mall";
        }
        return switch (e) {
            case MALL -> "mall";
            case HOTEL -> "hotel";
            case SCENIC -> "scenic";
            case FOOD -> "food";
            case FOOD_MEAL -> "foodMeal";
            case FOOD_COMPANY_MEAL -> "companyMeal";
            case LEISURE -> "leisure";
            case EXPLAIN -> "explain";
            case YEAR_CARD -> "yearcard";
            case STUDY -> "study";
            case CULTURE -> "culture";
            case VILLAGE -> "village";
            case HERITAGE -> "heritage";
        };
    }

    private static String fromClientBizType(String clientBizType) {
        if (StrUtil.isBlank(clientBizType)) {
            return null;
        }
        return switch (clientBizType) {
            case "mall" -> TradeBizTypeEnum.MALL.getValue();
            case "hotel" -> TradeBizTypeEnum.HOTEL.getValue();
            case "scenic" -> TradeBizTypeEnum.SCENIC.getValue();
            case "food" -> TradeBizTypeEnum.FOOD.getValue();
            case "foodMeal" -> TradeBizTypeEnum.FOOD_MEAL.getValue();
            case "companyMeal" -> TradeBizTypeEnum.FOOD_COMPANY_MEAL.getValue();
            case "leisure" -> TradeBizTypeEnum.LEISURE.getValue();
            case "explain" -> TradeBizTypeEnum.EXPLAIN.getValue();
            case "yearcard" -> TradeBizTypeEnum.YEAR_CARD.getValue();
            case "study" -> TradeBizTypeEnum.STUDY.getValue();
            case "culture" -> TradeBizTypeEnum.CULTURE.getValue();
            case "village" -> TradeBizTypeEnum.VILLAGE.getValue();
            case "heritage" -> TradeBizTypeEnum.HERITAGE.getValue();
            default -> clientBizType;
        };
    }

    private static String toClientOrderStatus(Integer status) {
        TradeOrderStatusEnum e = TradeOrderStatusEnum.fromValue(status);
        if (e == null) {
            return "pending_pay";
        }
        return switch (e) {
            case PENDING_PAY -> "pending_pay";
            case PAID -> "pending_ship";
            case FULFILLING -> "pending_receive";
            case FULFILLED, REFUNDED, CLOSED -> "completed";
            case REFUNDING -> "pending_receive";
            case CANCELLED -> "cancelled";
        };
    }

    private static List<Map<String, Object>> buildMerchantsBlock(TradeOrder o, List<TradeOrderItem> items) {
        List<Map<String, Object>> products = new ArrayList<>();
        if (items != null && !items.isEmpty()) {
            int seq = 1;
            for (TradeOrderItem it : items) {
                Map<String, Object> product = new LinkedHashMap<>();
                product.put("id", seq++);
                product.put("spuId", StrUtil.blankToDefault(it.getResourceRef(), ""));
                product.put("name", StrUtil.blankToDefault(it.getItemTitle(), "商品"));
                product.put("price", formatMoney(it.getUnitPrice()));
                product.put("image", "");
                int qty = it.getQuantity() == null || it.getQuantity() < 1 ? 1 : it.getQuantity();
                product.put("quantity", qty);
                product.put("specText", "");
                products.add(product);
            }
        } else {
            Map<String, Object> product = new LinkedHashMap<>();
            product.put("id", 1);
            product.put("spuId", "");
            product.put("name", StrUtil.blankToDefault(o.getTitle(), "订单"));
            product.put("price", formatMoney(o.getPayAmount() != null ? o.getPayAmount() : o.getTotalAmount()));
            product.put("image", "");
            product.put("quantity", 1);
            product.put("specText", "");
            products.add(product);
        }
        BigDecimal sumLine = BigDecimal.ZERO;
        for (TradeOrderItem it : items) {
            if (it.getLineAmount() != null) {
                sumLine = sumLine.add(it.getLineAmount());
            } else if (it.getUnitPrice() != null) {
                int q = it.getQuantity() == null || it.getQuantity() < 1 ? 1 : it.getQuantity();
                sumLine = sumLine.add(it.getUnitPrice().multiply(BigDecimal.valueOf(q)).setScale(2, RoundingMode.HALF_UP));
            }
        }
        if (sumLine.compareTo(BigDecimal.ZERO) <= 0) {
            sumLine = o.getPayAmount() != null ? o.getPayAmount() : o.getTotalAmount();
            if (sumLine == null) {
                sumLine = BigDecimal.ZERO;
            }
        }
        Map<String, Object> merchant = new LinkedHashMap<>();
        merchant.put("merchantId", merchantIdKey(o.getBizType()));
        merchant.put("merchantName", merchantLabel(o.getBizType()));
        merchant.put("products", products);
        merchant.put("subtotal", formatMoney(sumLine));
        return List.of(merchant);
    }

    private static String merchantIdKey(String bizType) {
        TradeBizTypeEnum e = TradeBizTypeEnum.fromValue(bizType);
        if (e == null) {
            return "trade";
        }
        return switch (e) {
            case MALL -> "mall";
            case HOTEL -> "hotel";
            case SCENIC -> "scenic";
            case FOOD -> "food";
            case FOOD_MEAL -> "foodMeal";
            case FOOD_COMPANY_MEAL -> "companyMeal";
            case LEISURE -> "leisure";
            case EXPLAIN -> "explain";
            case YEAR_CARD -> "yearcard";
            case STUDY -> "study";
            case CULTURE -> "culture";
            case VILLAGE -> "village";
            case HERITAGE -> "heritage";
        };
    }

    private static String merchantLabel(String bizType) {
        TradeBizTypeEnum e = TradeBizTypeEnum.fromValue(bizType);
        if (e == null) {
            return "订单";
        }
        return switch (e) {
            case MALL -> "官方店铺";
            case HOTEL -> "酒店民宿";
            case SCENIC -> "景区门票";
            case FOOD -> "餐饮美食";
            case FOOD_MEAL -> "个人订餐";
            case FOOD_COMPANY_MEAL -> "企业团餐";
            case LEISURE -> "休闲预约";
            case EXPLAIN -> "讲解预约";
            case YEAR_CARD -> "旅游年卡";
            case STUDY -> "研学预约";
            case CULTURE -> "活动报名";
            case VILLAGE -> "美丽乡村";
            case HERITAGE -> "非遗体验";
        };
    }

    private static String formatMoney(BigDecimal p) {
        if (p == null) {
            return "0.00";
        }
        return p.setScale(2, RoundingMode.HALF_UP).toPlainString();
    }

    /**
     * 将主单实体转为简要 VO（含垂直单号），供读切换或网关组装使用。
     */
    public TradeOrderInfoVO toInfoVo(TradeOrder order) {
        return toInfoVO(order);
    }

    private static TradeOrderInfoVO toInfoVO(TradeOrder order) {
        if (order == null) {
            return null;
        }
        TradeOrderInfoVO vo = new TradeOrderInfoVO();
        vo.setId(order.getId());
        vo.setOrderNo(order.getOrderNo());
        vo.setBizType(order.getBizType());
        vo.setMemberId(order.getMemberId());
        vo.setTitle(order.getTitle());
        vo.setTotalAmount(order.getTotalAmount());
        vo.setPayAmount(order.getPayAmount());
        vo.setStatus(order.getStatus());
        vo.setStatusName(TradeOrderStatusEnum.getDescByValue(order.getStatus()));
        vo.setPayOrderId(order.getPayOrderId());
        vo.setCreateDate(order.getCreateDate());
        vo.setVerticalOrderId(order.getVerticalOrderId());
        return vo;
    }
}
