package com.zioinfo.mes.shipping;

import com.zioinfo.mes.admin.AuditService;
import com.zioinfo.mes.inventory.InventoryService;
import com.zioinfo.mes.shipping.mapper.ShippingMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;

/**
 * 출하 서비스 — 출하지시·피킹·패킹·출하검사·출하확정(재고감소·납품서/거래명세서 발번).
 *
 * <p>상태 흐름: ORDERED → PICKING → PACKING → (INSPECTED) → SHIPPED.
 * <ul>
 *   <li>pick: 피킹 수량 기록(picked_qty), 상태 PICKING</li>
 *   <li>pack: 패킹 수량 기록(packed_qty), 상태 PACKING</li>
 *   <li>inspect: 출하검사(OQC) 결과 기록, 상태 INSPECTED — inspectionRequired 시 FAIL 이면 ship 차단</li>
 *   <li>ship: 출하확정 — 재고 감소(ISSUE) + 납품서/거래명세서 발번, 상태 SHIPPED</li>
 * </ul>
 */
@Service
@RequiredArgsConstructor
public class ShippingService {

    private final ShippingMapper mapper;
    private final InventoryService inventory;
    private final AuditService audit;

    public List<MesShipping> list(String status, String partnerCode, String keyword) {
        return mapper.findAll(status, partnerCode, keyword);
    }

    public MesShipping get(Long id) {
        MesShipping s = mapper.findById(id);
        if (s == null) throw new RuntimeException("ERR-SHIP-404: 출하 없음");
        return s;
    }

    public List<Map<String, Object>> statusCounts() {
        return mapper.countByStatus();
    }

    @Transactional
    public MesShipping create(MesShipping s, String actor) {
        if (s.getItemCode() == null || s.getItemCode().isBlank())
            throw new IllegalArgumentException("ERR-SHIP-400: itemCode 필수");
        if (s.getPlanQty() == null || s.getPlanQty() <= 0)
            throw new IllegalArgumentException("ERR-SHIP-400: planQty 는 0보다 커야 합니다");
        if (s.getShippingNo() == null || s.getShippingNo().isBlank())
            s.setShippingNo("SHP-" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE)
                + "-" + (System.currentTimeMillis() % 100000));
        if (mapper.countByNo(s.getShippingNo()) > 0)
            throw new RuntimeException("ERR-SHIP-409: 중복 출하지시번호");
        s.setStatus("ORDERED");
        if (s.getInspectionRequired() == null) s.setInspectionRequired(false);
        s.setCreatedBy(actor);
        mapper.insert(s);
        audit.log("SHIPPING_CREATE", s.getShippingNo(),
            "partner=" + s.getPartnerCode() + " item=" + s.getItemCode() + " qty=" + s.getPlanQty());
        return mapper.findById(s.getId());
    }

    @Transactional
    public MesShipping update(Long id, MesShipping s, String actor) {
        MesShipping cur = get(id);
        if (!"ORDERED".equals(cur.getStatus()))
            throw new RuntimeException("ERR-SHIP-409: ORDERED 상태에서만 수정 가능");
        s.setId(id);
        mapper.update(s);
        audit.log("SHIPPING_UPDATE", cur.getShippingNo(), "id=" + id);
        return mapper.findById(id);
    }

    /** 피킹 — 출고 대상 수량 확정. */
    @Transactional
    public MesShipping pick(Long id, double qty, String actor) {
        MesShipping s = get(id);
        if (qty <= 0) throw new IllegalArgumentException("ERR-SHIP-400: 피킹 수량은 0보다 커야 합니다");
        if (qty > s.getPlanQty())
            throw new RuntimeException("ERR-SHIP-409: 피킹 수량이 지시 수량을 초과");
        mapper.updatePicked(id, qty);
        audit.log("SHIPPING_PICK", s.getShippingNo(), "picked=" + qty);
        return mapper.findById(id);
    }

    /** 패킹. */
    @Transactional
    public MesShipping pack(Long id, double qty, String actor) {
        MesShipping s = get(id);
        double picked = s.getPickedQty() == null ? 0 : s.getPickedQty();
        if (qty <= 0) throw new IllegalArgumentException("ERR-SHIP-400: 패킹 수량은 0보다 커야 합니다");
        if (qty > picked)
            throw new RuntimeException("ERR-SHIP-409: 패킹 수량이 피킹 수량을 초과");
        mapper.updatePacked(id, qty);
        audit.log("SHIPPING_PACK", s.getShippingNo(), "packed=" + qty);
        return mapper.findById(id);
    }

    /** 출하검사(OQC). */
    @Transactional
    public MesShipping inspect(Long id, String result, String actor) {
        MesShipping s = get(id);
        String r = result == null ? "PASS" : result.toUpperCase();
        if (!r.equals("PASS") && !r.equals("FAIL"))
            throw new IllegalArgumentException("ERR-SHIP-400: 검사결과는 PASS/FAIL");
        mapper.updateInspection(id, r);
        audit.log("SHIPPING_INSPECT", s.getShippingNo(), "result=" + r);
        return mapper.findById(id);
    }

    /** 출하확정 — 재고 감소 + 납품서/거래명세서 발번. */
    @Transactional
    public MesShipping ship(Long id, String actor) {
        MesShipping s = get(id);
        if ("SHIPPED".equals(s.getStatus()))
            throw new RuntimeException("ERR-SHIP-409: 이미 출하확정됨");
        if ("CANCELLED".equals(s.getStatus()))
            throw new RuntimeException("ERR-SHIP-409: 취소된 출하");
        if (Boolean.TRUE.equals(s.getInspectionRequired())
                && !"PASS".equalsIgnoreCase(s.getInspectionResult())) {
            throw new RuntimeException("ERR-SHIP-409: 출하검사 합격(PASS) 후에만 출하확정 가능");
        }
        double qty = s.getPackedQty() != null && s.getPackedQty() > 0 ? s.getPackedQty()
                : s.getPickedQty() != null && s.getPickedQty() > 0 ? s.getPickedQty()
                : s.getPlanQty();
        // 재고 감소(원장 일원화: ISSUE 수불)
        inventory.outbound("ISSUE", s.getItemCode(), s.getWarehouseCode(), s.getLocationCode(),
            s.getLotNo(), qty, "SHIPPING", s.getShippingNo(), actor);
        String deliveryNo = "DLV-" + s.getShippingNo();
        String statementNo = "STM-" + s.getShippingNo();
        mapper.updateShip(id, qty, deliveryNo, statementNo);
        audit.log("SHIPPING_SHIP", s.getShippingNo(),
            "qty=" + qty + " 납품서=" + deliveryNo + " 거래명세서=" + statementNo);
        return mapper.findById(id);
    }

    @Transactional
    public MesShipping cancel(Long id, String actor) {
        MesShipping s = get(id);
        if ("SHIPPED".equals(s.getStatus()))
            throw new RuntimeException("ERR-SHIP-409: 출하확정 건은 취소 불가");
        mapper.updateStatus(id, "CANCELLED");
        audit.log("SHIPPING_CANCEL", s.getShippingNo(), "");
        return mapper.findById(id);
    }
}
