package com.example.demo.order;

import com.example.demo.common.AccountDefaultsProperties;
import com.example.demo.order.dto.OrderDtos;
import com.example.demo.product.entity.Inventory;
import com.example.demo.product.repo.InventoryRepository;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Service
public class OrderService {

    private final InventoryRepository inventoryRepository;
    private final AccountDefaultsProperties accountDefaults;
    private final JdbcTemplate jdbcTemplate;

    public OrderService(InventoryRepository inventoryRepository,
                        JdbcTemplate jdbcTemplate,
                        AccountDefaultsProperties accountDefaults) {
        this.inventoryRepository = inventoryRepository;
        this.jdbcTemplate = jdbcTemplate;
        this.accountDefaults = accountDefaults;
    }

    @Transactional
    public Object create(Long shopId, Long userId, OrderDtos.CreateOrderRequest req) {
        String type = req.type == null ? "" : req.type;
        boolean isSaleOut = "sale.out".equals(type) || "out".equals(type) || "sale".equals(type);
        boolean isPurchaseIn = "purchase.in".equals(type) || "in".equals(type);
        boolean isSaleReturn = "sale.return".equals(type);
        boolean isPurchaseReturn = "purchase.return".equals(type);
        boolean isSaleCollect = "sale.collect".equals(type);
        boolean isPurchasePay = "purchase.pay".equals(type);

        if (isSaleCollect || isPurchasePay) {
            java.util.List<OrderDtos.PaymentItem> payments = req.payments == null ? java.util.List.of() : req.payments;
            return createPayments(shopId, userId, payments, isSaleCollect ? "sale" : "purchase");
        }

        if (!(isSaleOut || isPurchaseIn || isSaleReturn || isPurchaseReturn)) throw new IllegalArgumentException("不支持的type");
        if (req.items == null || req.items.isEmpty()) throw new IllegalArgumentException("明细为空");

        // 后端重算金额
        final BigDecimal[] totalRef = new BigDecimal[]{BigDecimal.ZERO};
        for (OrderDtos.Item it : req.items) {
            BigDecimal qty = n(it.quantity);
            BigDecimal price = n(it.unitPrice);
            BigDecimal dr = n(it.discountRate);
            BigDecimal line = qty.multiply(price).multiply(BigDecimal.ONE.subtract(dr.divide(new BigDecimal("100"))));
            totalRef[0] = totalRef[0].add(scale2(line));
        }

        // 库存变动（保存即 approved）
        LocalDateTime now = nowUtc();
        for (OrderDtos.Item it : req.items) {
            Long pid = it.productId;
            Inventory inv = inventoryRepository.findById(pid).orElseGet(Inventory::new);
            inv.setProductId(pid);
            inv.setShopId(shopId);
            inv.setUserId(userId);
            BigDecimal cur = n(inv.getQuantity());
            BigDecimal delta = BigDecimal.ZERO;
            if (isSaleOut) delta = n(it.quantity).negate();
            if (isPurchaseIn) delta = n(it.quantity);
            if (isSaleReturn) delta = n(it.quantity); // 退货入库
            if (isPurchaseReturn) delta = n(it.quantity).negate(); // 退货出库
            BigDecimal next = cur.add(delta);
            if (isSaleOut && next.compareTo(BigDecimal.ZERO) < 0) {
                throw new IllegalStateException("库存不足");
            }
            inv.setQuantity(next);
            inv.setUpdatedAt(now);
            inventoryRepository.save(inv);

            // 写入库存流水（可选金额）
            String imSql = "INSERT INTO inventory_movements (shop_id,user_id,product_id,source_type,source_id,qty_delta,amount_delta,reason,tx_time,remark,created_at) VALUES (?,?,?,?,?,?,?,?,?,NULL,NOW())";
            String sourceType = isSaleOut ? "sale" : (isPurchaseIn ? "purchase" : (isSaleReturn ? "sale_return" : "purchase_return"));
            jdbcTemplate.update(imSql, shopId, userId, pid, sourceType, null, delta, null, null, java.sql.Timestamp.from(now.atZone(java.time.ZoneOffset.UTC).toInstant()));
        }

        String prefix = isSaleOut || isSaleReturn ? (isSaleReturn ? "SR" : "SO") : (isPurchaseReturn ? "PR" : "PO");
        String orderNo = OrderNumberGenerator.next(prefix);

        // 持久化订单头与明细（简化使用 JDBC）
        String headTable = isSaleOut ? "sales_orders" : (isPurchaseIn ? "purchase_orders" : (isSaleReturn ? "sales_return_orders" : "purchase_return_orders"));
        String itemTable = isSaleOut ? "sales_order_items" : (isPurchaseIn ? "purchase_order_items" : (isSaleReturn ? "sales_return_order_items" : "purchase_return_order_items"));

        // insert head（按表结构分别处理 sales*/purchase* 的 customer_id/supplier_id 列）
        KeyHolder kh = new GeneratedKeyHolder();
        boolean isSalesHead = headTable.startsWith("sales");
        boolean isPurchaseHead = headTable.startsWith("purchase");
        String headSql;
        if (isSalesHead) {
            headSql = "INSERT INTO " + headTable + " (shop_id,user_id,customer_id,order_no,order_time,status,amount,paid_amount,remark,created_at,updated_at) " +
                    "VALUES (?,?,?,?,?, 'approved', ?, 0, ?, NOW(), NOW())";
        } else if (isPurchaseHead) {
            headSql = "INSERT INTO " + headTable + " (shop_id,user_id,supplier_id,order_no,order_time,status,amount,paid_amount,remark,created_at,updated_at) " +
                    "VALUES (?,?,?,?,?, 'approved', ?, 0, ?, NOW(), NOW())";
        } else {
            // 理论不会到这里，兜底为 sales 结构
            headSql = "INSERT INTO " + headTable + " (shop_id,user_id,order_no,order_time,status,amount,paid_amount,remark,created_at,updated_at) " +
                    "VALUES (?,?,?,?,'approved', ?, 0, ?, NOW(), NOW())";
        }

        Long customerId = req.customerId;
        Long supplierId = req.supplierId;
        jdbcTemplate.update(con -> {
            java.sql.PreparedStatement ps = con.prepareStatement(headSql, new String[]{"id"});
            int idx = 1;
            ps.setLong(idx++, shopId);
            ps.setLong(idx++, userId);
            if (isSalesHead) {
                ps.setObject(idx++, customerId, java.sql.Types.BIGINT);
            } else if (isPurchaseHead) {
                ps.setObject(idx++, supplierId, java.sql.Types.BIGINT);
            }
            ps.setString(idx++, orderNo);
            ps.setTimestamp(idx++, java.sql.Timestamp.from(now.atZone(java.time.ZoneOffset.UTC).toInstant()));
            ps.setBigDecimal(idx++, scale2(totalRef[0]));
            ps.setString(idx, req.remark);
            return ps;
        }, kh);
        Number orderKey = kh.getKey();
        Long orderId = (orderKey == null ? null : orderKey.longValue());

        // insert items（销售类有折扣列，采购类无折扣列）
        boolean itemsHasDiscount = isSaleOut || isSaleReturn;
        String itemSql = itemsHasDiscount
                ? ("INSERT INTO " + itemTable + " (order_id,product_id,quantity,unit_price,discount_rate,amount) VALUES (?,?,?,?,?,?)")
                : ("INSERT INTO " + itemTable + " (order_id,product_id,quantity,unit_price,amount) VALUES (?,?,?,?,?)");
        for (OrderDtos.Item it : req.items) {
            BigDecimal qty = n(it.quantity);
            BigDecimal price = n(it.unitPrice);
            BigDecimal dr = n(it.discountRate);
            BigDecimal line = scale2(qty.multiply(price).multiply(BigDecimal.ONE.subtract(dr.divide(new BigDecimal("100")))));
            if (itemsHasDiscount) {
                jdbcTemplate.update(itemSql, orderId, it.productId, qty, price, dr, line);
            } else {
                jdbcTemplate.update(itemSql, orderId, it.productId, qty, price, line);
            }
        }

        // 维护供应商应付：采购入库增加，应付=+amount；采购退货减少，应付=-amount
        if (isPurchaseHead && supplierId != null) {
            java.math.BigDecimal delta = scale2(totalRef[0]);
            if (isPurchaseReturn) delta = delta.negate();
            adjustSupplierPayable(supplierId, delta);
        }

        return new OrderDtos.CreateOrderResponse(orderId, orderNo);
    }

    @Transactional
    public OrderDtos.CreatePaymentsResponse createPayments(Long shopId, Long userId, java.util.List<OrderDtos.PaymentItem> req, String bizType) {
        ensureDefaultAccounts(shopId, userId);
        List<Long> ids = new ArrayList<>();
        if (req == null) return new OrderDtos.CreatePaymentsResponse(ids);
        String direction = "sale".equals(bizType) ? "in" : "out";
        for (OrderDtos.PaymentItem p : req) {
            // 收/付款必须绑定订单（资金类不在此接口中处理）
            if (("sale".equals(bizType) || "purchase".equals(bizType)) && p.orderId == null) {
                throw new IllegalArgumentException("收/付款必须绑定订单");
            }
            Long accountId = resolveAccountId(shopId, userId, p.method);
            KeyHolder kh = new GeneratedKeyHolder();
            String sql = "INSERT INTO payments (shop_id,user_id,biz_type,biz_id,account_id,direction,amount,pay_time,remark,created_at) " +
                    "VALUES (?,?,?,?,?,?,?,NOW(),NULL,NOW())";
            jdbcTemplate.update(con -> {
                java.sql.PreparedStatement ps = con.prepareStatement(sql, new String[]{"id"});
                ps.setLong(1, shopId);
                ps.setLong(2, userId);
                ps.setString(3, bizType);
                if (p.orderId == null) ps.setNull(4, java.sql.Types.BIGINT); else ps.setLong(4, p.orderId);
                ps.setLong(5, accountId);
                ps.setString(6, direction);
                ps.setBigDecimal(7, n(p.amount));
                return ps;
            }, kh);
            Number payKey = kh.getKey();
            Long pid = (payKey == null ? null : payKey.longValue());
            if (pid != null) ids.add(pid);

            // 若挂单，累加已付
            if (p.orderId != null) {
                String table = "sale".equals(bizType) ? "sales_orders" : "purchase_orders";
                jdbcTemplate.update("UPDATE " + table + " SET paid_amount = paid_amount + ? WHERE id = ?", n(p.amount), p.orderId);

                // 采购付款联动应付：应付 -= 付款金额
                if ("purchase".equals(bizType)) {
                    java.util.List<Long> sids = jdbcTemplate.query("SELECT supplier_id FROM purchase_orders WHERE id=?", (rs,rn)-> rs.getLong(1), p.orderId);
                    if (!sids.isEmpty() && sids.get(0) != null) {
                        adjustSupplierPayable(sids.get(0), n(p.amount).negate());
                    }
                }
            }
        }
        return new OrderDtos.CreatePaymentsResponse(ids);
    }

    @Transactional
    public void voidOrder(Long shopId, Long userId, Long id, String type) {
        // type: sale.out / purchase.in / sale.return / purchase.return
        String headTable;
        String itemTable;
        boolean revertIncrease; // true 表示作废时库存应减少（原先增加），false 表示应增加（原先减少）
        if ("sale.out".equals(type)) { headTable = "sales_orders"; itemTable = "sales_order_items"; revertIncrease = false; }
        else if ("purchase.in".equals(type)) { headTable = "purchase_orders"; itemTable = "purchase_order_items"; revertIncrease = true; }
        else if ("sale.return".equals(type)) { headTable = "sales_return_orders"; itemTable = "sales_return_order_items"; revertIncrease = true; }
        else if ("purchase.return".equals(type)) { headTable = "purchase_return_orders"; itemTable = "purchase_return_order_items"; revertIncrease = false; }
        else throw new IllegalArgumentException("不支持的type");

        // 查询明细
        List<java.util.Map<String,Object>> rows = jdbcTemplate.queryForList("SELECT product_id, quantity FROM " + itemTable + " WHERE order_id = ?", id);
        // 回滚库存
        LocalDateTime now = nowUtc();
        for (java.util.Map<String,Object> r : rows) {
            Long pid = ((Number)r.get("product_id")).longValue();
            java.math.BigDecimal qty = new java.math.BigDecimal(r.get("quantity").toString());
            Inventory inv = inventoryRepository.findById(pid).orElseGet(Inventory::new);
            inv.setProductId(pid);
            inv.setShopId(shopId);
            inv.setUserId(userId);
            java.math.BigDecimal delta = revertIncrease ? qty.negate() : qty; // 与创建时相反
            inv.setQuantity(n(inv.getQuantity()).add(delta));
            inv.setUpdatedAt(now);
            inventoryRepository.save(inv);
        }
        // 更新状态
        jdbcTemplate.update("UPDATE " + headTable + " SET status='void' WHERE id = ?", id);

        // 采购单作废回滚应付
        if ("purchase.out".equals(type) || "purchase.in".equals(type) || "purchase.return".equals(type)) {
            boolean purchaseIn = "purchase.in".equals(type);
            boolean purchaseRet = "purchase.return".equals(type);
            if (purchaseIn || purchaseRet) {
                java.util.List<java.util.Map<String,Object>> h = jdbcTemplate.queryForList("SELECT supplier_id, amount FROM " + headTable + " WHERE id=?", id);
                if (!h.isEmpty()) {
                    Long sid = ((Number)h.get(0).get("supplier_id")).longValue();
                    java.math.BigDecimal amt = new java.math.BigDecimal(h.get(0).get("amount").toString());
                    java.math.BigDecimal delta = purchaseIn ? amt.negate() : amt; // 入库作废应付减少；退货作废应付增加
                    adjustSupplierPayable(sid, delta);
                }
            }
        }
    }

    public java.util.Map<String,Object> list(Long shopId, String biz, String type, String kw, int page, int size, String startDate, String endDate) {
        StringBuilder sql = new StringBuilder();
        java.util.List<Object> ps = new java.util.ArrayList<>();
        ps.add(shopId);

        if ("purchase".equals(biz)) {
            // 进货单（含退货按状态过滤），返回驼峰并带供应商名称
            sql.append("SELECT po.id, po.order_no AS orderNo, po.order_time AS orderTime, po.amount, s.name AS supplierName,\n")
               .append("CASE WHEN po.status='returned' THEN 'purchase.return' ELSE 'purchase.in' END AS docType\n")
               .append("FROM purchase_orders po\n")
               .append("LEFT JOIN suppliers s ON s.id = po.supplier_id\n")
               .append("WHERE po.shop_id=?");
            if ("purchase.return".equals(type)) {
                sql.append(" AND po.status='returned'");
            }
            if (kw != null && !kw.isBlank()) { sql.append(" AND (po.order_no LIKE ?)"); ps.add('%' + kw + '%'); }
            if (startDate != null && !startDate.isBlank()) { sql.append(" AND po.order_time>=?"); ps.add(java.sql.Timestamp.valueOf(startDate + " 00:00:00")); }
            if (endDate != null && !endDate.isBlank()) { sql.append(" AND po.order_time<=?"); ps.add(java.sql.Timestamp.valueOf(endDate + " 23:59:59")); }
            sql.append(" ORDER BY po.order_time DESC LIMIT ? OFFSET ?");
        } else { // 默认销售
            // 销售单，返回驼峰并带客户名称
            sql.append("SELECT so.id, so.order_no AS orderNo, so.order_time AS orderTime, so.amount, c.name AS customerName, 'sale.out' AS docType\n")
               .append("FROM sales_orders so\n")
               .append("LEFT JOIN customers c ON c.id = so.customer_id\n")
               .append("WHERE so.shop_id=?");
            if (kw != null && !kw.isBlank()) { sql.append(" AND (so.order_no LIKE ?)"); ps.add('%' + kw + '%'); }
            if (startDate != null && !startDate.isBlank()) { sql.append(" AND so.order_time>=?"); ps.add(java.sql.Timestamp.valueOf(startDate + " 00:00:00")); }
            if (endDate != null && !endDate.isBlank()) { sql.append(" AND so.order_time<=?"); ps.add(java.sql.Timestamp.valueOf(endDate + " 23:59:59")); }
            sql.append(" ORDER BY so.order_time DESC LIMIT ? OFFSET ?");
        }

        ps.add(size);
        ps.add(page * size);
        java.util.List<java.util.Map<String,Object>> list = jdbcTemplate.queryForList(sql.toString(), ps.toArray());

        // 汇总
        StringBuilder sumSql;
        java.util.List<Object> sumPs = new java.util.ArrayList<>();
        sumPs.add(shopId);
        if ("purchase".equals(biz)) {
            sumSql = new StringBuilder("SELECT COALESCE(SUM(amount),0) FROM purchase_orders WHERE shop_id=?");
            if ("purchase.return".equals(type)) { sumSql.append(" AND status='returned'"); }
            if (kw != null && !kw.isBlank()) { sumSql.append(" AND (order_no LIKE ?)"); sumPs.add('%' + kw + '%'); }
            if (startDate != null && !startDate.isBlank()) { sumSql.append(" AND order_time>=?"); sumPs.add(java.sql.Timestamp.valueOf(startDate + " 00:00:00")); }
            if (endDate != null && !endDate.isBlank()) { sumSql.append(" AND order_time<=?"); sumPs.add(java.sql.Timestamp.valueOf(endDate + " 23:59:59")); }
        } else {
            sumSql = new StringBuilder("SELECT COALESCE(SUM(amount),0) FROM sales_orders WHERE shop_id=?");
            if (kw != null && !kw.isBlank()) { sumSql.append(" AND (order_no LIKE ?)"); sumPs.add('%' + kw + '%'); }
            if (startDate != null && !startDate.isBlank()) { sumSql.append(" AND order_time>=?"); sumPs.add(java.sql.Timestamp.valueOf(startDate + " 00:00:00")); }
            if (endDate != null && !endDate.isBlank()) { sumSql.append(" AND order_time<=?"); sumPs.add(java.sql.Timestamp.valueOf(endDate + " 23:59:59")); }
        }
        java.math.BigDecimal total = jdbcTemplate.queryForObject(sumSql.toString(), java.math.BigDecimal.class, sumPs.toArray());
        java.util.Map<String,Object> resp = new java.util.HashMap<>();
        resp.put("list", list);
        resp.put("totalAmount", total == null ? java.math.BigDecimal.ZERO : total);
        return resp;
    }

    public java.util.Map<String,Object> listPayments(Long shopId, String direction, String bizType, Long accountId, String kw, int page, int size, String startDate, String endDate) {
        StringBuilder sql = new StringBuilder("SELECT p.id, p.biz_type AS bizType, p.account_id, a.name AS accountName, p.direction, p.amount, p.pay_time AS orderTime,\n" +
                "CASE \n" +
                "  WHEN p.biz_type='sale' AND p.direction='in' THEN 'sale.collect' \n" +
                "  WHEN p.biz_type='purchase' AND p.direction='out' THEN 'purchase.pay' \n" +
                "  WHEN p.biz_type='other' AND p.direction='in' THEN 'other.income' \n" +
                "  WHEN p.biz_type='other' AND p.direction='out' THEN 'other.expense' \n" +
                "  ELSE CONCAT(p.biz_type, '.', p.direction) END AS docType\n" +
                "FROM payments p LEFT JOIN accounts a ON a.id=p.account_id WHERE p.shop_id=?");
        java.util.List<Object> ps = new java.util.ArrayList<>();
        ps.add(shopId);
        if (direction != null && !direction.isBlank()) { sql.append(" AND p.direction=?"); ps.add(direction); }
        if (bizType != null && !bizType.isBlank()) { sql.append(" AND p.biz_type=?"); ps.add(bizType); }
        if (accountId != null) { sql.append(" AND p.account_id=?"); ps.add(accountId); }
        if (kw != null && !kw.isBlank()) { sql.append(" AND (CAST(p.id AS CHAR) LIKE ?)"); ps.add('%'+kw+'%'); }
        if (startDate != null && !startDate.isBlank()) { sql.append(" AND p.pay_time>=?"); ps.add(java.sql.Timestamp.valueOf(startDate + " 00:00:00")); }
        if (endDate != null && !endDate.isBlank()) { sql.append(" AND p.pay_time<=?"); ps.add(java.sql.Timestamp.valueOf(endDate + " 23:59:59")); }
        sql.append(" ORDER BY p.pay_time DESC LIMIT ? OFFSET ?");
        ps.add(size); ps.add(page * size);
        java.util.List<java.util.Map<String,Object>> list = jdbcTemplate.queryForList(sql.toString(), ps.toArray());
        StringBuilder sumSql = new StringBuilder("SELECT COALESCE(SUM(p.amount),0) FROM payments p WHERE p.shop_id=?");
        java.util.List<Object> sumPs = new java.util.ArrayList<>(); sumPs.add(shopId);
        if (direction != null && !direction.isBlank()) { sumSql.append(" AND p.direction=?"); sumPs.add(direction); }
        if (bizType != null && !bizType.isBlank()) { sumSql.append(" AND p.biz_type=?"); sumPs.add(bizType); }
        if (accountId != null) { sumSql.append(" AND p.account_id=?"); sumPs.add(accountId); }
        if (kw != null && !kw.isBlank()) { sumSql.append(" AND (CAST(p.id AS CHAR) LIKE ?)"); sumPs.add('%'+kw+'%'); }
        if (startDate != null && !startDate.isBlank()) { sumSql.append(" AND p.pay_time>=?"); sumPs.add(java.sql.Timestamp.valueOf(startDate + " 00:00:00")); }
        if (endDate != null && !endDate.isBlank()) { sumSql.append(" AND p.pay_time<=?"); sumPs.add(java.sql.Timestamp.valueOf(endDate + " 23:59:59")); }
        java.math.BigDecimal total = jdbcTemplate.queryForObject(sumSql.toString(), java.math.BigDecimal.class, sumPs.toArray());
        java.util.Map<String,Object> resp = new java.util.HashMap<>(); resp.put("list", list); resp.put("totalAmount", total == null ? java.math.BigDecimal.ZERO : total); return resp;
    }

    public java.util.Map<String,Object> listOtherTransactions(Long shopId, String type, Long accountId, String kw, int page, int size, String startDate, String endDate) {
        StringBuilder sql = new StringBuilder("SELECT ot.id, ot.`type`, CONCAT('other.', ot.`type`) AS docType, ot.account_id, a.name AS accountName, ot.amount, ot.tx_time AS txTime, ot.remark FROM other_transactions ot LEFT JOIN accounts a ON a.id=ot.account_id WHERE ot.shop_id=?");
        java.util.List<Object> ps = new java.util.ArrayList<>(); ps.add(shopId);
        if (type != null && !type.isBlank()) { sql.append(" AND ot.`type`=?"); ps.add(type); }
        if (accountId != null) { sql.append(" AND ot.account_id=?"); ps.add(accountId); }
        if (kw != null && !kw.isBlank()) { sql.append(" AND (ot.remark LIKE ? OR ot.category LIKE ?)"); ps.add('%'+kw+'%'); ps.add('%'+kw+'%'); }
        if (startDate != null && !startDate.isBlank()) { sql.append(" AND ot.tx_time>=?"); ps.add(java.sql.Timestamp.valueOf(startDate + " 00:00:00")); }
        if (endDate != null && !endDate.isBlank()) { sql.append(" AND ot.tx_time<=?"); ps.add(java.sql.Timestamp.valueOf(endDate + " 23:59:59")); }
        sql.append(" ORDER BY ot.tx_time DESC LIMIT ? OFFSET ?"); ps.add(size); ps.add(page * size);
        java.util.List<java.util.Map<String,Object>> list = jdbcTemplate.queryForList(sql.toString(), ps.toArray());
        StringBuilder sumSql = new StringBuilder("SELECT COALESCE(SUM(ot.amount),0) FROM other_transactions ot WHERE ot.shop_id=?");
        java.util.List<Object> sumPs = new java.util.ArrayList<>(); sumPs.add(shopId);
        if (type != null && !type.isBlank()) { sumSql.append(" AND ot.`type`=?"); sumPs.add(type); }
        if (accountId != null) { sumSql.append(" AND ot.account_id=?"); sumPs.add(accountId); }
        if (kw != null && !kw.isBlank()) { sumSql.append(" AND (ot.remark LIKE ? OR ot.category LIKE ?)"); sumPs.add('%'+kw+'%'); sumPs.add('%'+kw+'%'); }
        if (startDate != null && !startDate.isBlank()) { sumSql.append(" AND ot.tx_time>=?"); sumPs.add(java.sql.Timestamp.valueOf(startDate + " 00:00:00")); }
        if (endDate != null && !endDate.isBlank()) { sumSql.append(" AND ot.tx_time<=?"); sumPs.add(java.sql.Timestamp.valueOf(endDate + " 23:59:59")); }
        java.math.BigDecimal total = jdbcTemplate.queryForObject(sumSql.toString(), java.math.BigDecimal.class, sumPs.toArray());
        java.util.Map<String,Object> resp = new java.util.HashMap<>(); resp.put("list", list); resp.put("totalAmount", total == null ? java.math.BigDecimal.ZERO : total); return resp;
    }

    @org.springframework.transaction.annotation.Transactional
    public java.util.Map<String, Object> createOtherTransaction(Long shopId, Long userId, OrderDtos.CreateOtherTransactionRequest req) {
        if (req == null) throw new IllegalArgumentException("请求为空");
        String type = req.type == null ? null : req.type.trim().toLowerCase();
        if (!"income".equals(type) && !"expense".equals(type)) throw new IllegalArgumentException("type 仅支持 income/expense");
        if (req.accountId == null) throw new IllegalArgumentException("账户必选");
        java.math.BigDecimal amt = n(req.amount);
        if (amt.compareTo(java.math.BigDecimal.ZERO) <= 0) throw new IllegalArgumentException("金额需大于0");
        java.time.LocalDateTime when;
        if (req.txTime == null || req.txTime.isBlank()) when = nowUtc();
        else {
            // 允许 yyyy-MM-dd 或完整时间
            try {
                if (req.txTime.length() == 10) when = java.time.LocalDate.parse(req.txTime).atStartOfDay();
                else when = java.time.LocalDateTime.parse(req.txTime);
            } catch (Exception e) { when = nowUtc(); }
        }
        final java.sql.Timestamp whenTs = java.sql.Timestamp.from(when.atZone(java.time.ZoneOffset.UTC).toInstant());

        // 插入 other_transactions
        org.springframework.jdbc.support.GeneratedKeyHolder kh = new org.springframework.jdbc.support.GeneratedKeyHolder();
        String sql = "INSERT INTO other_transactions (shop_id,user_id,`type`,category,counterparty_type,counterparty_id,account_id,amount,tx_time,remark,created_at,updated_at) " +
                "VALUES (?,?,?,?,?,?,?,?,?, ?, NOW(), NOW())";
        jdbcTemplate.update(con -> {
            java.sql.PreparedStatement ps = con.prepareStatement(sql, new String[]{"id"});
            int i = 1;
            ps.setLong(i++, shopId);
            ps.setLong(i++, userId);
            ps.setString(i++, type);
            ps.setString(i++, req.category);
            if (req.counterpartyType == null) ps.setNull(i++, java.sql.Types.VARCHAR); else ps.setString(i++, req.counterpartyType);
            if (req.counterpartyId == null) ps.setNull(i++, java.sql.Types.BIGINT); else ps.setLong(i++, req.counterpartyId);
            ps.setLong(i++, req.accountId);
            ps.setBigDecimal(i++, scale2(amt));
            ps.setTimestamp(i++, whenTs);
            ps.setString(i, req.remark);
            return ps;
        }, kh);
        Number key = kh.getKey();
        Long id = key == null ? null : key.longValue();

        // 写支付流水，联动账户余额
        String direction = "income".equals(type) ? "in" : "out";
        org.springframework.jdbc.support.GeneratedKeyHolder payKh = new org.springframework.jdbc.support.GeneratedKeyHolder();
        final Long idForPayment = id;
        jdbcTemplate.update(con -> {
            java.sql.PreparedStatement ps = con.prepareStatement(
                    "INSERT INTO payments (shop_id,user_id,biz_type,biz_id,account_id,direction,amount,pay_time,remark,created_at) VALUES (?,?,?,?,?,?,?,?,?,NOW())",
                    new String[]{"id"}
            );
            int i = 1;
            ps.setLong(i++, shopId);
            ps.setLong(i++, userId);
            ps.setString(i++, "other");
            if (idForPayment == null) ps.setNull(i++, java.sql.Types.BIGINT); else ps.setLong(i++, idForPayment);
            ps.setLong(i++, req.accountId);
            ps.setString(i++, direction);
            ps.setBigDecimal(i++, scale2(amt));
            ps.setTimestamp(i++, whenTs);
            ps.setString(i, req.remark);
            return ps;
        }, payKh);

        // 联动账户余额：收入加，支出减
        java.math.BigDecimal delta = "income".equals(type) ? amt : amt.negate();
        jdbcTemplate.update("UPDATE accounts SET balance = balance + ?, updated_at=NOW() WHERE id=? AND shop_id=?", scale2(delta), req.accountId, shopId);

        java.util.Map<String,Object> resp = new java.util.HashMap<>();
        resp.put("id", id);
        return resp;
    }

    public java.util.Map<String,Object> listInventoryLogs(Long shopId, Long productId, String reason, String kw, int page, int size, String startDate, String endDate) {
        StringBuilder sql = new StringBuilder("SELECT id, product_id, qty_delta, amount_delta, COALESCE(amount_delta,0) AS amount, reason, tx_time AS txTime, remark FROM inventory_movements WHERE shop_id=?");
        java.util.List<Object> ps = new java.util.ArrayList<>(); ps.add(shopId);
        if (productId != null) { sql.append(" AND product_id=?"); ps.add(productId); }
        if (reason != null && !reason.isBlank()) { sql.append(" AND reason=?"); ps.add(reason); }
        if (kw != null && !kw.isBlank()) { sql.append(" AND (remark LIKE ? OR source_type LIKE ?)"); ps.add('%'+kw+'%'); ps.add('%'+kw+'%'); }
        if (startDate != null && !startDate.isBlank()) { sql.append(" AND tx_time>=?"); ps.add(java.sql.Timestamp.valueOf(startDate + " 00:00:00")); }
        if (endDate != null && !endDate.isBlank()) { sql.append(" AND tx_time<=?"); ps.add(java.sql.Timestamp.valueOf(endDate + " 23:59:59")); }
        sql.append(" ORDER BY tx_time DESC LIMIT ? OFFSET ?"); ps.add(size); ps.add(page * size);
        java.util.List<java.util.Map<String,Object>> list = jdbcTemplate.queryForList(sql.toString(), ps.toArray());
        StringBuilder sumSql = new StringBuilder("SELECT COALESCE(SUM(COALESCE(amount_delta,0)),0) FROM inventory_movements WHERE shop_id=?");
        java.util.List<Object> sumPs = new java.util.ArrayList<>(); sumPs.add(shopId);
        if (productId != null) { sumSql.append(" AND product_id=?"); sumPs.add(productId); }
        if (reason != null && !reason.isBlank()) { sumSql.append(" AND reason=?"); sumPs.add(reason); }
        if (kw != null && !kw.isBlank()) { sumSql.append(" AND (remark LIKE ? OR source_type LIKE ?)"); sumPs.add('%'+kw+'%'); sumPs.add('%'+kw+'%'); }
        if (startDate != null && !startDate.isBlank()) { sumSql.append(" AND tx_time>=?"); sumPs.add(java.sql.Timestamp.valueOf(startDate + " 00:00:00")); }
        if (endDate != null && !endDate.isBlank()) { sumSql.append(" AND tx_time<=?"); sumPs.add(java.sql.Timestamp.valueOf(endDate + " 23:59:59")); }
        java.math.BigDecimal total = jdbcTemplate.queryForObject(sumSql.toString(), java.math.BigDecimal.class, sumPs.toArray());
        java.util.Map<String,Object> resp = new java.util.HashMap<>(); resp.put("list", list); resp.put("totalAmount", total == null ? java.math.BigDecimal.ZERO : total); return resp;
    }

    // 详情：销售单
    public java.util.Map<String,Object> getSalesOrderDetail(Long shopId, Long id) {
        java.util.List<java.util.Map<String,Object>> heads = jdbcTemplate.queryForList(
                "SELECT so.id, so.order_no AS orderNo, so.order_time AS orderTime, so.status, so.amount, so.paid_amount AS paidAmount, so.customer_id AS customerId, c.name AS customerName, so.remark\n" +
                        "FROM sales_orders so LEFT JOIN customers c ON c.id=so.customer_id WHERE so.shop_id=? AND so.id=?",
                shopId, id);
        if (heads.isEmpty()) return java.util.Map.of();
        java.util.Map<String,Object> head = heads.get(0);
        java.util.List<java.util.Map<String,Object>> items = jdbcTemplate.queryForList(
                "SELECT i.id, i.product_id AS productId, p.name, p.spec, i.quantity, i.unit_price AS unitPrice, i.discount_rate AS discountRate, i.amount\n" +
                        "FROM sales_order_items i JOIN products p ON p.id=i.product_id WHERE i.order_id=?",
                id);
        java.util.List<java.util.Map<String,Object>> pays = jdbcTemplate.queryForList(
                "SELECT p.id, p.amount, p.pay_time AS payTime, p.account_id AS accountId, a.name AS accountName, p.direction\n" +
                        "FROM payments p LEFT JOIN accounts a ON a.id=p.account_id WHERE p.biz_type='sale' AND p.biz_id=?",
                id);
        java.util.Map<String,Object> resp = new java.util.HashMap<>(head);
        resp.put("items", items);
        resp.put("payments", pays);
        return resp;
    }

    // 详情：进货单
    public java.util.Map<String,Object> getPurchaseOrderDetail(Long shopId, Long id) {
        java.util.List<java.util.Map<String,Object>> heads = jdbcTemplate.queryForList(
                "SELECT po.id, po.order_no AS orderNo, po.order_time AS orderTime, po.status, po.amount, po.paid_amount AS paidAmount, po.supplier_id AS supplierId, s.name AS supplierName, po.remark\n" +
                        "FROM purchase_orders po LEFT JOIN suppliers s ON s.id=po.supplier_id WHERE po.shop_id=? AND po.id=?",
                shopId, id);
        if (heads.isEmpty()) return java.util.Map.of();
        java.util.Map<String,Object> head = heads.get(0);
        java.util.List<java.util.Map<String,Object>> items = jdbcTemplate.queryForList(
                "SELECT i.id, i.product_id AS productId, p.name, p.spec, i.quantity, i.unit_price AS unitPrice, i.amount\n" +
                        "FROM purchase_order_items i JOIN products p ON p.id=i.product_id WHERE i.order_id=?",
                id);
        java.util.List<java.util.Map<String,Object>> pays = jdbcTemplate.queryForList(
                "SELECT p.id, p.amount, p.pay_time AS payTime, p.account_id AS accountId, a.name AS accountName, p.direction\n" +
                        "FROM payments p LEFT JOIN accounts a ON a.id=p.account_id WHERE p.biz_type='purchase' AND p.biz_id=?",
                id);
        java.util.Map<String,Object> resp = new java.util.HashMap<>(head);
        resp.put("items", items);
        resp.put("payments", pays);
        return resp;
    }

    private static BigDecimal n(BigDecimal v) { return v == null ? BigDecimal.ZERO : v; }
    private static BigDecimal scale2(BigDecimal v) { return v.setScale(2, java.math.RoundingMode.HALF_UP); }
    private static LocalDateTime nowUtc() { return LocalDateTime.now(java.time.Clock.systemUTC()); }

    private void adjustSupplierPayable(Long supplierId, java.math.BigDecimal delta) {
        if (supplierId == null || delta == null) return;
        jdbcTemplate.update("UPDATE suppliers SET ap_payable = ap_payable + ? WHERE id = ?", delta, supplierId);
    }

    private void ensureDefaultAccounts(Long shopId, Long userId) {
        // 为 cash/bank/wechat/alipay 分别确保存在一条账户记录；按 type→name 顺序检查，避免同名唯一冲突
        ensureAccount(shopId, userId, "cash", accountDefaults.getCashName());
        ensureAccount(shopId, userId, "bank", accountDefaults.getBankName());
        ensureAccount(shopId, userId, "wechat", accountDefaults.getWechatName());
        ensureAccount(shopId, userId, "alipay", accountDefaults.getAlipayName());
    }

    private void ensureAccount(Long shopId, Long userId, String type, String name) {
        List<Long> byType = jdbcTemplate.query("SELECT id FROM accounts WHERE shop_id=? AND type=? LIMIT 1", (rs,rn)->rs.getLong(1), shopId, type);
        if (!byType.isEmpty()) return;
        List<Long> byName = jdbcTemplate.query("SELECT id FROM accounts WHERE shop_id=? AND name=? LIMIT 1", (rs,rn)->rs.getLong(1), shopId, name);
        if (!byName.isEmpty()) return; // 已有同名则直接复用，无需再插
        jdbcTemplate.update("INSERT INTO accounts (shop_id,user_id,name,type,balance,status,created_at,updated_at) VALUES (?,?,?,?,0,1,NOW(),NOW())",
                shopId, userId, name, type);
    }

    private Long resolveAccountId(Long shopId, Long userId, String method) {
        String type = "cash";
        if ("bank".equalsIgnoreCase(method)) type = "bank";
        if ("wechat".equalsIgnoreCase(method)) type = "wechat";
        if ("alipay".equalsIgnoreCase(method)) type = "alipay";
        String name = accountDefaults.getCashName();
        if ("bank".equals(type)) name = accountDefaults.getBankName();
        else if ("wechat".equals(type)) name = accountDefaults.getWechatName();
        else if ("alipay".equals(type)) name = accountDefaults.getAlipayName();
        // 先按 type 查
        List<Long> byType = jdbcTemplate.query("SELECT id FROM accounts WHERE shop_id=? AND type=? LIMIT 1", (rs,rn)->rs.getLong(1), shopId, type);
        if (!byType.isEmpty()) return byType.get(0);
        // 再按 name 查，避免同名唯一冲突
        List<Long> byName = jdbcTemplate.query("SELECT id FROM accounts WHERE shop_id=? AND name=? LIMIT 1", (rs,rn)->rs.getLong(1), shopId, name);
        if (!byName.isEmpty()) return byName.get(0);
        // 都没有再插入
        jdbcTemplate.update("INSERT INTO accounts (shop_id,user_id,name,type,balance,status,created_at,updated_at) VALUES (?,?,?,?,0,1,NOW(),NOW())",
                shopId, userId, name, type);
        // 插入后按 type 读取
        List<Long> recheck = jdbcTemplate.query("SELECT id FROM accounts WHERE shop_id=? AND type=? LIMIT 1", (rs,rn)->rs.getLong(1), shopId, type);
        if (!recheck.isEmpty()) return recheck.get(0);
        throw new IllegalStateException("账户映射失败: " + method);
    }
}


