package com.shzg.project.worn.service.impl;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Cell;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Table;
import com.itextpdf.layout.properties.TextAlignment;
import com.itextpdf.layout.properties.VerticalAlignment;
import org.apache.commons.io.IOUtils;
import com.shzg.common.utils.DateUtils;
import com.shzg.common.utils.SecurityUtils;
import com.shzg.common.utils.StringUtils;
import com.shzg.project.unique.domain.WornUniqueCode;
import com.shzg.project.unique.domain.WornUniqueCodeEvent;
import com.shzg.project.unique.mapper.WornUniqueCodeEventMapper;
import com.shzg.project.unique.mapper.WornUniqueCodeMapper;
import com.shzg.project.unique.mapper.WornUniqueCodeMaterialMapper;
import com.shzg.project.worn.domain.WornInboundItem;
import com.shzg.project.worn.domain.dto.WornInboundPartialFinishDTO;
import com.shzg.project.worn.domain.dto.WornInboundPartialFinishItemDTO;
import com.shzg.project.worn.mapper.WornInboundItemMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;
import com.shzg.project.worn.mapper.WornInboundBillMapper;
import com.shzg.project.worn.domain.WornInboundBill;
import com.shzg.project.worn.service.IWornInboundBillService;
import org.springframework.transaction.annotation.Transactional;


/**
 * 入库库存Service业务层处理
 * 
 * @author shzg
 * @date 2026-03-26
 */
@Service
public class WornInboundBillServiceImpl implements IWornInboundBillService
{
    @Autowired
    private WornInboundBillMapper wornInboundBillMapper;

    @Autowired
    private WornInboundItemMapper wornInboundItemMapper;

    @Autowired
    private WornUniqueCodeMapper wornUniqueCodeMapper;

    @Autowired
    private WornUniqueCodeEventMapper eventMapper;

    @Autowired
    private ResourceLoader resourceLoader;

    @Value("${worn.pdf-font-locations:classpath:fonts/simhei.ttf,/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc,/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc,/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc,/usr/share/fonts/truetype/arphic/ukai.ttc,C:/Windows/Fonts/simhei.ttf,C:/Windows/Fonts/simsun.ttc}")
    private String pdfFontLocations;

    @Autowired
    private  WornUniqueCodeMaterialMapper wornUniqueCodeMaterialMapper;


    /**
     * 查询入库库存
     * 
     * @param id 入库库存主键
     * @return 入库库存
     */
    @Override
    public WornInboundBill selectWornInboundBillById(Long id)
    {
        return wornInboundBillMapper.selectWornInboundBillById(id);
    }

    /**
     * 查询入库库存列表
     * 
     * @param wornInboundBill 入库库存
     * @return 入库库存
     */
    @Override
    public List<WornInboundBill> selectWornInboundBillList(WornInboundBill wornInboundBill)
    {
        return wornInboundBillMapper.selectWornInboundBillList(wornInboundBill);
    }

    /**
     * 新增入库库存
     *
     * @param wornInboundBill 入库库存
     * @return 结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertWornInboundBill(WornInboundBill wornInboundBill)
    {
        /* ================== 1. 参数校验 ================== */
        if (wornInboundBill == null)
        {
            throw new RuntimeException("入库单参数不能为空");
        }
        if (wornInboundBill.getItemList() == null || wornInboundBill.getItemList().isEmpty())
        {
            throw new RuntimeException("入库明细不能为空");
        }

        /* ================== 2. 当前用户 ================== */
        Long userId = SecurityUtils.getLoginUser().getUser().getUserId();
        Long deptId = SecurityUtils.getLoginUser().getUser().getDeptId();

        wornInboundBill.setProjectId(deptId);
        /* ================== 3. 主表赋值 ================== */
        wornInboundBill.setCreateTime(DateUtils.getNowDate());
        wornInboundBill.setCreateBy(String.valueOf(userId));
        wornInboundBill.setInboundTime(DateUtils.getNowDate());
        wornInboundBill.setBillType("0");
        wornInboundBill.setStatus("0");
        wornInboundBill.setIsDelete("0");

        // 生成单号
        if (wornInboundBill.getBillNo() == null || "".equals(wornInboundBill.getBillNo().trim()))
        {
            wornInboundBill.setBillNo("WR" + System.currentTimeMillis());
        }

        /* ================== 4. 插入主表 ================== */
        int rows = wornInboundBillMapper.insertWornInboundBill(wornInboundBill);
        if (rows <= 0)
        {
            throw new RuntimeException("入库单新增失败");
        }

        Long billId = wornInboundBill.getId();
        String billNo = wornInboundBill.getBillNo();

        if (billId == null)
        {
            throw new RuntimeException("入库单新增失败，ID未回填");
        }

        /* ================== 5. 插入明细 ================== */
        List<WornInboundItem> itemList = wornInboundBill.getItemList();
        for (WornInboundItem item : itemList){
            if (item == null){
                continue;
            }

            item.setBillId(billId);
            item.setBillNo(billNo);
            item.setCreateTime(DateUtils.getNowDate());
            item.setCreateBy(String.valueOf(userId));
            item.setIsDelete(item.getIsDelete());

            int itemRows = wornInboundItemMapper.insertWornInboundItem(item);
            if (itemRows <= 0){
                throw new RuntimeException("入库明细新增失败");
            }

            /* ================== 6. 唯一码联动 ================== */
            if (item.getUniqueCode() != null){
                Integer code = item.getUniqueCode();

                Long uniqueId = wornUniqueCodeMapper.selectIdByCode(code);
                if (uniqueId == null)
                {
                    throw new RuntimeException("唯一码不存在：" + code+"，请添加唯一码");
                }

                WornUniqueCode update = new WornUniqueCode();
                update.setCode(code);
                update.setStatus("1");
                update.setUpdateBy(String.valueOf(userId));
                update.setUpdateTime(DateUtils.getNowDate());

                int updateRows = wornUniqueCodeMapper.updateByCode(update);
                if (updateRows <= 0)
                {
                    throw new RuntimeException("唯一码状态更新失败：" + code);
                }
                /* ================== 插入物料详情 ================== */
                WornUniqueCodeEvent event = new WornUniqueCodeEvent();
                event.setUniqueCodeId(uniqueId);
                event.setEventType("1");
                event.setEventStatus("1");
                event.setEventDesc("生成入库单号");
                event.setOperatorId(userId);
                event.setCreateBy(String.valueOf(userId));
                event.setCreateTime(DateUtils.getNowDate());
                event.setIsDelete("0");

                int eventRows = eventMapper.insertWornUniqueCodeEvent(event);
                if (eventRows <= 0)
                {
                    throw new RuntimeException("唯一码事件记录失败：" + code);
                }
            }
        }

        return 1;
    }

    /**
     * 修改入库库存
     * 
     * @param wornInboundBill 入库库存
     * @return 结果
     */
    @Override
    public int updateWornInboundBill(WornInboundBill wornInboundBill)
    {
        wornInboundBill.setUpdateTime(DateUtils.getNowDate());
        Long userId = SecurityUtils.getLoginUser().getUser().getUserId();
        wornInboundBill.setUpdateBy(String.valueOf(userId));
        return wornInboundBillMapper.updateWornInboundBill(wornInboundBill);
    }

    /**
     * 批量删除入库库存
     * 
     * @param ids 需要删除的入库库存主键
     * @return 结果
     */
    @Override
    public int deleteWornInboundBillByIds(Long[] ids)
    {
        return wornInboundBillMapper.deleteWornInboundBillByIds(ids);
    }

    /**
     * 删除入库库存信息
     * 
     * @param id 入库库存主键
     * @return 结果
     */
    @Override
    public int deleteWornInboundBillById(Long id)
    {
        return wornInboundBillMapper.deleteWornInboundBillById(id);
    }
    /**
     * 提交入库单申请
     *
     * */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int inboundFinish(WornInboundPartialFinishDTO dto){
        if (dto == null)
        {
            throw new RuntimeException("参数不能为空");
        }
        if (dto.getBillNo() == null || "".equals(dto.getBillNo().trim()))
        {
            throw new RuntimeException("单据号不能为空");
        }
        if (dto.getItemList() == null || dto.getItemList().isEmpty())
        {
            throw new RuntimeException("本次入库明细不能为空");
        }

        Long userId = SecurityUtils.getLoginUser().getUser().getUserId();
        String userName = SecurityUtils.getLoginUser().getUser().getNickName();
        Date now = DateUtils.getNowDate();

        // 1. 查询主表
        WornInboundBill bill = wornInboundBillMapper.selectWornInboundBillByBillNo(dto.getBillNo());
        if (bill == null)
        {
            throw new RuntimeException("入库单不存在：" + dto.getBillNo());
        }
        if ("1".equals(bill.getIsDelete()))
        {
            throw new RuntimeException("入库单已删除，不能入库");
        }

        // 2. 先更新本次入库的明细（防重复）
        for (WornInboundPartialFinishItemDTO itemDTO : dto.getItemList())
        {
            if (itemDTO == null || itemDTO.getId() == null)
            {
                throw new RuntimeException("明细ID不能为空");
            }

            // 查询明细
            WornInboundItem item = wornInboundItemMapper.selectWornInboundItemById(itemDTO.getId());
            if (item == null)
            {
                throw new RuntimeException("明细不存在，ID=" + itemDTO.getId());
            }
            if (!bill.getId().equals(item.getBillId()))
            {
                throw new RuntimeException("明细不属于当前单据，ID=" + itemDTO.getId());
            }

            // 更新明细状态=已入库，且只能从未入库改为已入库
            WornInboundItem updateItem = new WornInboundItem();
            updateItem.setId(itemDTO.getId());
            updateItem.setRemark(itemDTO.getRemark());
            updateItem.setStatus("1");
            updateItem.setUpdateBy(String.valueOf(userId));
            updateItem.setUpdateTime(now);

            int itemRows = wornInboundItemMapper.updateInboundItemStatus(updateItem);
            if (itemRows <= 0)
            {
                throw new RuntimeException("该明细已入库，请勿重复操作，ID=" + itemDTO.getId());
            }

            // 处理唯一码
            if (item.getUniqueCode() != null){
                WornUniqueCode uniqueCode = wornUniqueCodeMapper.selectByCode(item.getUniqueCode());
                if (uniqueCode == null)
                {
                    throw new RuntimeException("唯一码不存在：" + item.getUniqueCode());
                }

                // 只能从 1=生成入库单号 改成 2=快速入库
                WornUniqueCode updateUnique = new WornUniqueCode();
                updateUnique.setCode(item.getUniqueCode());
                updateUnique.setStatus("2");
                updateUnique.setUpdateBy(String.valueOf(userId));
                updateUnique.setUpdateTime(now);

                int uniqueRows = wornUniqueCodeMapper.updateInboundStatusByCode(updateUnique);
                if (uniqueRows <= 0)
                {
                    throw new RuntimeException("唯一码已入库或状态异常：" + item.getUniqueCode());
                }

                // 写事件
                WornUniqueCodeEvent event = new WornUniqueCodeEvent();
                event.setUniqueCodeId(uniqueCode.getId());
                event.setEventType("2");      // 快速入库
                event.setEventStatus("1");
                event.setEventDesc("入库成功");
                event.setOperatorId(userId);
                event.setOperatorName(userName);
                event.setCreateBy(String.valueOf(userId));
                event.setCreateTime(now);
                event.setIsDelete("0");
                event.setRemark("单号：" + dto.getBillNo() + "，唯一码：" + item.getUniqueCode());

                int eventRows = eventMapper.insertWornUniqueCodeEvent(event);
                if (eventRows <= 0)
                {
                    throw new RuntimeException("唯一码事件记录新增失败：" + item.getUniqueCode());
                }
            }
        }

        // 3. 更新主表仓库信息、备注
        WornInboundBill updateBill = new WornInboundBill();
        updateBill.setId(bill.getId());
        updateBill.setWarehouseCode(dto.getWarehouseCode());
        updateBill.setWarehouseName(dto.getWarehouseName());
        updateBill.setAreaCode(dto.getAreaCode());
        updateBill.setAreaName(dto.getAreaName());
        updateBill.setRemark(dto.getBillRemark());
        updateBill.setUpdateBy(String.valueOf(userId));
        updateBill.setUpdateTime(now);

        // 4. 判断该单据下是否全部明细已入库
        int notInboundCount = wornInboundItemMapper.countNotInboundByBillId(bill.getId());
        if (notInboundCount == 0)
        {
            // 全部入库
            updateBill.setStatus("1");   // 已完成
            updateBill.setBillType("1"); // 入库成功
        }
        else
        {
            // 部分入库
            updateBill.setStatus("2");   // 部分入库
            // billType 暂不改，保持原单据类型
        }

        int billRows = wornInboundBillMapper.updateWornInboundBillById(updateBill);
        if (billRows <= 0)
        {
            throw new RuntimeException("主表更新失败");
        }

        return 1;
    }
    /**
     * 单据作废接口
     * */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int voidBill(String billNo)
    {
        if (StringUtils.isEmpty(billNo))
        {
            throw new RuntimeException("单据号不能为空");
        }

        Long userId = SecurityUtils.getLoginUser().getUser().getUserId();
        String userName = SecurityUtils.getLoginUser().getUser().getNickName();
        Date now = DateUtils.getNowDate();

        // 1️⃣ 查询主表
        WornInboundBill bill = wornInboundBillMapper.selectWornInboundBillByBillNo(billNo);
        if (bill == null)
        {
            throw new RuntimeException("单据不存在");
        }

        if ("4".equals(bill.getBillType()))
        {
            throw new RuntimeException("单据已作废，请勿重复操作");
        }

        // ❗（建议加）有已入库的不允许作废
        List<WornInboundItem> itemList = wornInboundItemMapper.selectWornInboundItemByBillId(bill.getId());
        if (itemList == null || itemList.isEmpty())
        {
            throw new RuntimeException("没有明细，无法作废");
        }

        for (WornInboundItem item : itemList)
        {
            if ("1".equals(item.getStatus()))
            {
                throw new RuntimeException("存在已入库明细，不允许整单作废");
            }
        }

        // 2️⃣ 处理明细 + 唯一码
        for (WornInboundItem item : itemList){
            // 2.1 明细作废
            WornInboundItem updateItem = new WornInboundItem();
            updateItem.setId(item.getId());
            updateItem.setStatus("9"); // 明细作废
            updateItem.setUpdateBy(String.valueOf(userId));
            updateItem.setUpdateTime(now);

            int itemRows = wornInboundItemMapper.updateItemToVoid(updateItem);
            if (itemRows <= 0){
                throw new RuntimeException("明细作废失败，ID=" + item.getId());
            }

            // 2.2 唯一码作废
            if (item.getUniqueCode() != null){
                WornUniqueCode unique = wornUniqueCodeMapper.selectByCode(item.getUniqueCode());
                if (unique != null)
                {
                    // 🔥 状态改为 9（作废）
                    WornUniqueCode updateUnique = new WornUniqueCode();
                    updateUnique.setCode(item.getUniqueCode());
                    updateUnique.setStatus("9");
                    updateUnique.setUpdateBy(String.valueOf(userId));
                    updateUnique.setUpdateTime(now);

                    int uniqueRows = wornUniqueCodeMapper.updateVoidStatusByCode(updateUnique);
                    if (uniqueRows <= 0)
                    {
                        throw new RuntimeException("唯一码作废失败：" + item.getUniqueCode());
                    }
                    /* ================== 3.3 更新 material 为删除 ================== */
                    WornUniqueCode wornUniqueCode = new WornUniqueCode();
                    wornUniqueCode.setCode(item.getUniqueCode());
                    List<WornUniqueCode> list = wornUniqueCodeMapper.selectWornUniqueCodeList(wornUniqueCode);
                    if (list != null && list.size() > 0){
                        //一个唯一码只有一条
                        Long uniqueCodeId = list.get(0).getId();
                        int uniqueRow = wornUniqueCodeMaterialMapper.updateDeleteByUniqueCodeId(uniqueCodeId);
                    }
                    /* ================== 3.3 写事件（9=作废） ================== */
                    WornUniqueCodeEvent event = new WornUniqueCodeEvent();
                    event.setUniqueCodeId(unique.getId());
                    event.setEventType("9");      // 作废事件
                    event.setEventStatus("9");    // 作废状态
                    event.setEventDesc("入库单作废");
                    event.setOperatorId(userId);
                    event.setOperatorName(userName);
                    event.setCreateBy(String.valueOf(userId));
                    event.setCreateTime(now);
                    event.setIsDelete("0");
                    event.setRemark("作废单号：" + billNo + "，唯一码：" + item.getUniqueCode());

                    int eventRows = eventMapper.insertWornUniqueCodeEvent(event);
                    if (eventRows <= 0)
                    {
                        throw new RuntimeException("唯一码事件记录失败：" + item.getUniqueCode());
                    }
                }
            }
        }

        // 3️⃣ 主表作废
        WornInboundBill updateBill = new WornInboundBill();
        updateBill.setId(bill.getId());
        updateBill.setBillType("4"); // 已作废
        updateBill.setStatus("9");   // 作废状态
        updateBill.setUpdateBy(String.valueOf(userId));
        updateBill.setUpdateTime(now);

        int billRows = wornInboundBillMapper.updateWornInboundBillById(updateBill);
        if (billRows <= 0)
        {
            throw new RuntimeException("主表作废失败");
        }

        return 1;
    }
    @Override
    public byte[] generatePdf(Long id) throws Exception
    {
        WornInboundBill bill = wornInboundBillMapper.selectWornInboundBillById(id);
        if (bill == null)
        {
            throw new RuntimeException("入库单不存在");
        }

        List<WornInboundItem> itemList = wornInboundItemMapper.selectWornInboundItemByBillId(id);

        ByteArrayOutputStream out = new ByteArrayOutputStream();

        PdfWriter writer = new PdfWriter(out);
        PdfDocument pdf = new PdfDocument(writer);
        Document document = new Document(pdf, PageSize.A4);

        // ===== 中文字体 =====
        PdfFont font = createPdfFont();
        document.setFont(font);

        // ===== 标题 =====
        document.add(new Paragraph("智汇APP报表系统")
                .setTextAlignment(TextAlignment.CENTER)
                .setFontSize(16));

        document.add(new Paragraph("入库单")
                .setTextAlignment(TextAlignment.CENTER)
                .setFontSize(14)
                .setBold());

        document.add(new Paragraph("\n"));

        // ===== 表格 =====
        // 新增“存储设施类型”后，列数改为11列
        float[] widths = {2, 6, 6, 5, 4, 4, 4, 2, 3, 3, 4};
        Table table = new Table(widths).useAllAvailableWidth();

        // ===== 表头 =====
        table.addCell(cell("序号"));
        table.addCell(cell("入库批次编号"));
        table.addCell(cell("入库时间"));
        table.addCell(cell("存储设施类型"));
        table.addCell(cell("物料名称"));
        table.addCell(cell("规格型号"));
        table.addCell(cell("入库数量"));
        table.addCell(cell("单位"));
        table.addCell(cell("操作人"));
        table.addCell(cell("备注"));
        table.addCell(cell("唯一码"));

        // ===== 时间格式 =====
        String datePart = "";
        String timePart = "";
        if (bill.getInboundTime() != null)
        {
            datePart = new SimpleDateFormat("yyyy-MM-dd").format(bill.getInboundTime());
            timePart = new SimpleDateFormat("HH:mm:ss").format(bill.getInboundTime());
        }

        // ===== 数据 =====
        int index = 1;
        BigDecimal total = BigDecimal.ZERO;

        for (WornInboundItem item : itemList)
        {
            table.addCell(cell(String.valueOf(index++)));
            table.addCell(cell(bill.getBillNo()));

            Cell timeCell = new Cell()
                    .add(new Paragraph(datePart + "\n" + timePart))
                    .setTextAlignment(TextAlignment.CENTER)
                    .setVerticalAlignment(VerticalAlignment.MIDDLE);
            table.addCell(timeCell);

            // ===== 新增：存储设施类型（warehouse_name）=====
            table.addCell(cell(bill.getWarehouseName()));

            table.addCell(cell(item.getMaterialName()));
            table.addCell(cell(buildSpecificationText(item)));
            table.addCell(cell(item.getQuantity() == null ? "" : item.getQuantity().toString()));
            table.addCell(cell(item.getUnitName()));
            table.addCell(cell(item.getOperatorName()));
            table.addCell(cell(item.getRemark()));
            table.addCell(cell(item.getUniqueCode() == null ? "" : item.getUniqueCode().toString()));

            if (item.getQuantity() != null)
            {
                total = total.add(item.getQuantity());
            }
        }

        // ===== 合计 =====
        // 前6列合并：序号、入库批次编号、入库时间、存储设施类型、物料名称、规格型号
        table.addCell(new Cell(1, 6)
                .add(new Paragraph("合计"))
                .setTextAlignment(TextAlignment.CENTER)
                .setVerticalAlignment(VerticalAlignment.MIDDLE));

        // 入库数量
        table.addCell(cell(total.toString()));

        // 剩余4列留空：单位、操作人、备注、唯一码
        table.addCell(new Cell(1, 4)
                .add(new Paragraph(""))
                .setTextAlignment(TextAlignment.CENTER)
                .setVerticalAlignment(VerticalAlignment.MIDDLE));

        document.add(table);

        document.close();
        System.out.println("PDF生成成功，字节长度：" + out.size());
        return out.toByteArray();
    }

    private Cell cell(String text)
    {
        return new Cell()
                .add(new Paragraph(text == null ? "" : text))
                .setTextAlignment(TextAlignment.CENTER)
                .setVerticalAlignment(VerticalAlignment.MIDDLE);
    }

    private String buildSpecificationText(WornInboundItem item)
    {
        String specification = item.getSpecification();
        String model = item.getModel();

        if (StringUtils.isNotEmpty(specification) && StringUtils.isNotEmpty(model)) {
            return specification + "/" + model;
        }
        if (StringUtils.isNotEmpty(specification)) {
            return specification;
        }
        if (StringUtils.isNotEmpty(model)) {
            return model;
        }
        return "";
    }

    private PdfFont createPdfFont() throws IOException
    {
        for (String location : pdfFontLocations.split(",")) {
            String trimmed = location.trim();
            if (trimmed.isEmpty()) {
                continue;
            }

            byte[] fontBytes = loadFontBytes(trimmed);
            if (fontBytes == null) {
                continue;
            }

            return PdfFontFactory.createFont(
                    fontBytes,
                    "Identity-H",
                    PdfFontFactory.EmbeddingStrategy.PREFER_EMBEDDED
            );
        }

        throw new IllegalStateException("未找到可用的PDF中文字体，请检查 worn.pdf-font-locations 配置");
    }

    private byte[] loadFontBytes(String location) throws IOException
    {
        if (location.startsWith("classpath:")) {
            Resource resource = resourceLoader.getResource(location);
            if (!resource.exists()) {
                return null;
            }
            return IOUtils.toByteArray(resource.getInputStream());
        }

        Path path = location.startsWith("file:")
                ? Paths.get(location.substring("file:".length()))
                : Paths.get(location);

        if (!Files.exists(path) || Files.isDirectory(path)) {
            return null;
        }

        return Files.readAllBytes(path);
    }
}




