package com.cf.imes.module.executor.service.funds.invoice;


import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.Header;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.common.util.Assert.AssertUtils;
import com.cf.imes.framework.common.util.date.LocalDateTimeUtils;
import com.cf.imes.framework.common.util.json.JsonUtils;
import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.framework.mybatis.core.query.QueryWrapperX;
import com.cf.imes.framework.security.core.LoginUser;
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
import com.cf.imes.module.executor.controller.admin.funds.invoice.vo.*;
import com.cf.imes.module.executor.dal.dataobject.funds.balancedetails.IncomeExpenseDetailsDO;
import com.cf.imes.module.executor.dal.dataobject.funds.invoice.InvoiceRecordsDO;
import com.cf.imes.module.executor.dal.dataobject.funds.purchase.PurchaseRecordDO;
import com.cf.imes.module.executor.dal.mysql.funds.balancedetails.IncomeExpenseDetailsMapper;
import com.cf.imes.module.executor.dal.mysql.funds.invoice.InvoiceRecordsMapper;
import com.cf.imes.module.executor.dal.mysql.funds.purchase.PurchaseRecordMapper;
import com.cf.imes.module.executor.enums.amount.InvoiceStatusEnum;
import com.cf.imes.module.executor.enums.amount.TradeTypeEnum;
import com.cf.imes.module.system.api.funds.SystemFundsApi;
import com.cf.imes.module.system.api.funds.dto.InvoiceTitleInfoDTO;
import com.cf.imes.module.system.api.organ.OrganApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.cf.imes.framework.common.util.json.JsonUtils.*;
import static com.cf.imes.framework.security.core.util.SecurityFrameworkUtils.getUserOrganId;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.*;

/**
 * @author 发票管理接口 实现类
 */
@Slf4j
@Service
public class InvoiceServiceImpl implements InvoiceService{


    @Resource
    private PurchaseRecordMapper purchaseRecordMapper;

    @Resource
    private IncomeExpenseDetailsMapper incomeExpenseDetailsMapper;

    @Resource
    private InvoiceRecordsMapper invoiceRecordsMapper;


    @Resource
    private SystemFundsApi systemFundsApi;

    @Resource
    private OrganApi organApi;


    @Resource
    private InvoiceFilePath invoiceFilePath;



    private static final String UTF8_CHARSET = CharsetUtil.UTF_8;
    private static final String ATTACHMENT_PREFIX = "attachment; filename=";





    @Override
    public InvoiceAmountRespVO getInvoiceAmount(Long organId) {

        InvoiceAmountRespVO invoiceAmountRespVO = purchaseRecordMapper.selectOrganInvoice(organId);

        BigDecimal invoiceableAmount = systemFundsApi.getOrganAmount(organId).getCheckedData().getInvoiceableAmount();

        return invoiceAmountRespVO.setInvocable(invoiceableAmount);

    }





    @Override
    public PageResult<InvoiceDetailsRespVO> getInvoiceDetails(InvoiceDetailsPageReqVO pageReqVO) {

        PageDTO<InvoiceDetailsRespVO> page = new PageDTO<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());

        List<Integer> invoiceStatus = new ArrayList<>();
        invoiceStatus.add(InvoiceStatusEnum.INVOICABLE.getStatus());
        invoiceStatus.add(InvoiceStatusEnum.INVOICINGFAILED.getStatus());

        QueryWrapperX<IncomeExpenseDetailsDO> queryWrapperX = new QueryWrapperX<>();

        pageReqVO.setCreateTime(LocalDateTimeUtils.generateTimeSection(pageReqVO.getCreateTime()));
        queryWrapperX
                .eq("ied.organId",getUserOrganId())
                .eq("ied.deleted",false)
                .eq("ied.trade_type", TradeTypeEnum.EXPENSES.getType())
                .in("pr.is_invocing", invoiceStatus)
                .betweenIfPresent("ied.create_time",pageReqVO.getCreateTime())
                .betweenIfPresent("ied.cash_amount_change",pageReqVO.getAmount());

        IPage<InvoiceDetailsRespVO> respVOIPage = incomeExpenseDetailsMapper.selectOrganInvoicable(page, queryWrapperX);

        List<InvoiceDetailsRespVO> records = respVOIPage.getRecords();
        if (CollUtil.isEmpty(records)) {
            return new PageResult<>();
        }

        return new PageResult<>(records, respVOIPage.getTotal());

    }






    @Override
    public PageResult<InvoiceRecordsRespVO> getInvoiceRecords(InvoiceDetailsPageReqVO pageReqVO) {

        pageReqVO.setCreateTime(LocalDateTimeUtils.generateTimeSection(pageReqVO.getCreateTime()));
        pageReqVO.setInvoiceTime(LocalDateTimeUtils.generateTimeSection(pageReqVO.getInvoiceTime()));

        PageResult<InvoiceRecordsDO> pageResult = invoiceRecordsMapper.selectInvoiceRecordsPage(pageReqVO);
        List<InvoiceRecordsDO> resultList = pageResult.getList();

        if(CollUtil.isEmpty(resultList)){
            return new PageResult<>();
        }

        List<InvoiceRecordsRespVO> recordsRespVOS = BeanUtils.toBean(resultList, InvoiceRecordsRespVO.class);

        return new PageResult<>(recordsRespVOS, pageResult.getTotal());


    }





    @Override
    public void insetInvoiceRecords(InvoiceRecordsReqVO reqVO) {

        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
        assert loginUser != null;

        String organName = organApi.getOrganDetails(loginUser.getOrganId()).getCheckedData().getName();

        InvoiceTitleInfoDTO titleInfoDTO = systemFundsApi.getInvoiceTitle(reqVO.getInvoiceTitleId()).getCheckedData();

        invoiceRecordsMapper.insert(InvoiceRecordsDO.builder()
                .userId(loginUser.getId())
                .organName(organName)
                .purchaseIds(JsonUtils.toJsonString(reqVO.getPurchaseRecordId()))
                .invoiceAmount(reqVO.getInvoiceAmount())
                .invoiceTitleId(reqVO.getInvoiceTitleId())
                .invoiceTitleInfo(zipString(toJsonString(titleInfoDTO)))
                .status(InvoiceStatusEnum.PENDINGINVOICING.getStatus())
                .build());


        // 更新购买记录表的发票状态
        purchaseRecordMapper.update(new LambdaUpdateWrapper<PurchaseRecordDO>()
                .eq(PurchaseRecordDO::getOrganId, loginUser.getOrganId())
                .eq(PurchaseRecordDO::getDeleted,false)
                .in(PurchaseRecordDO::getId,reqVO.getPurchaseRecordId())
                .set(PurchaseRecordDO::getIsInvocing, InvoiceStatusEnum.PENDINGINVOICING.getStatus()));


        // 减少组织余额的可开票的金额
        systemFundsApi.reduceOrganAmount(loginUser.getOrganId(),reqVO.getInvoiceAmount());


    }




    @Override
    public InvoiceDetailsRespVO getInvoiceIncome(Long invoiceId) {

        InvoiceRecordsDO invoiceRecordsDO = invoiceRecordsMapper.selectById(invoiceId);

        AssertUtils.notEmpty(invoiceRecordsDO,INVOICE_NO_EXIST);

        String purchaseIds = Optional.ofNullable(invoiceRecordsDO.getPurchaseIds()).orElse("[]");

        List<Long> purchaseIdList = JSON.parseArray(purchaseIds).toJavaList(Long.class).stream().distinct().toList();

        List<InvoicePurchaseRecord> invoicePurchaseRecords = purchaseRecordMapper.selectPurchaseRecord(purchaseIdList, getUserOrganId());

        InvoiceTitleInfo invoiceTitleInfo = parseObject(unzipString(invoiceRecordsDO.getInvoiceTitleInfo()), InvoiceTitleInfo.class);

        return InvoiceDetailsRespVO.builder()
                .invoicePurchaseRecords(invoicePurchaseRecords)
                .invoiceTitleInfo(invoiceTitleInfo)
                .build();

    }





    @Override
    public void invoicing(InvoiceInvoicingReqVO reqVO) {

        InvoiceRecordsDO invoiceRecordsDO = validateInvoiceExists(reqVO.getId());

        if(invoiceRecordsDO.getStatus().equals(InvoiceStatusEnum.INVOICINGFAILED.getStatus())){
            throw exception(THIS_INVOICE_IS_FAIL);
        }


//        if(invoiceRecordsDO.getStatus().equals(InvoiceStatusEnum.INVOICINGSUCCESSFUL.getStatus())){
//            throw exception(THIS_INVOICE_IS_SUCCESS);
//        }


        String purchaseIds = Optional.ofNullable(invoiceRecordsDO.getPurchaseIds()).orElse("[]");

        List<Long> purchaseIdList = JSON.parseArray(purchaseIds).toJavaList(Long.class).stream().distinct().toList();

        List<PurchaseRecordDO> purchaseRecordDOS = validatePurchaseRecordExists(purchaseIdList);


        if(Boolean.TRUE.equals(reqVO.getIsInvoicing())){

            invoiceRecordsDO.setStatus(InvoiceStatusEnum.INVOICINGSUCCESSFUL.getStatus());
            invoiceRecordsDO.setRemark(reqVO.getRemark());

            purchaseRecordDOS.forEach(f->f.setIsInvocing(InvoiceStatusEnum.INVOICINGSUCCESSFUL.getStatus()));

        }else {

            // 增加组织余额可开票的金额
            systemFundsApi.addOrganAmount(invoiceRecordsDO.getOrganId(),invoiceRecordsDO.getInvoiceAmount());

            invoiceRecordsDO.setStatus(InvoiceStatusEnum.INVOICINGFAILED.getStatus());
            String remark = Optional.ofNullable(reqVO.getRemark()).orElseThrow(() -> new ServiceException(INVOICING_REJECT_REASON));
            invoiceRecordsDO.setRemark(remark);

            purchaseRecordDOS.forEach(f->f.setIsInvocing(InvoiceStatusEnum.INVOICABLE.getStatus()));

        }


        invoiceRecordsDO.setInvoiceAttachmentPath(reqVO.getInvoiceAttachmentPath());
        invoiceRecordsDO.setInvoiceNo(reqVO.getInvoiceNo());

        invoiceRecordsMapper.updateById(invoiceRecordsDO);

        purchaseRecordMapper.updateBatch(purchaseRecordDOS);

    }





    @Override
    public void uploadInvoice(MultipartFile file, Long invoiceId) {

        AssertUtils.notEmpty(file,INVOICE_FILE_IS_NULL);

        // 生成存储目录
        Path invoiceDir = Paths.get(invoiceFilePath.getFilePath(),String.valueOf(invoiceId));
        if (!Files.exists(invoiceDir)) {
            try {
                Files.createDirectories(invoiceDir);
            } catch (IOException e) {
                log.error("发票附件目录创建失败" + e.getMessage());
                throw exception(INVOICE_FILE_UPLOAD_FAILED);
            }
        }


        // 删除旧文件（确保目录中只有一个文件）
        try (Stream<Path> files = Files.list(invoiceDir)) {
            files.forEach(existingFile -> {
                try {
                    Files.delete(existingFile);
                } catch (IOException e) {
                    log.error("发票附件旧文件删除失败：" + e.getMessage());
                }
            });
        } catch (IOException e) {
            log.error("发票附件旧文件删除失败: " + e.getMessage());
            throw exception(INVOICE_FILE_UPLOAD_FAILED);
        }

        // 保存新文件
        String fileName = Objects.requireNonNull(file.getOriginalFilename());
        Path filePath = invoiceDir.resolve(fileName);

        try (InputStream inputStream = file.getInputStream()) {
            Files.copy(inputStream, filePath, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException e) {
            log.error("发票附件上传失败: " + e.getMessage());
            throw exception(INVOICE_FILE_UPLOAD_FAILED);
        }

    }





    @Override
    public void downloadInvoice(Long invoiceId, HttpServletResponse response) {

        Path invoiceDir = Paths.get(invoiceFilePath.getFilePath(), String.valueOf(invoiceId));

        if (!Files.exists(invoiceDir)) {
            throw exception(INVOICE_FILE_IS_NULL_NOT_DOWNLOAD);
        }


        // 获取目录中的文件
        File[] files = invoiceDir.toFile().listFiles();
        if (files == null || files.length == 0) {
            throw exception(INVOICE_FILE_IS_NULL_NOT_DOWNLOAD);
        }


        // 取第一个文件
        File file = files[0];

        try (InputStream inputStream = new FileInputStream(file);
             OutputStream outputStream = response.getOutputStream()) {

            response.setCharacterEncoding(UTF8_CHARSET);
            response.setContentType("application/octet-stream; charset=UTF-8");
            response.setHeader(Header.CONTENT_DISPOSITION.getValue(), ATTACHMENT_PREFIX + URLEncoder.encode(file.getName(), UTF8_CHARSET));
            response.setContentLengthLong(file.length());

            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            outputStream.flush();

        } catch (IOException e) {
            log.error("发票附件下载异常："+ e.getMessage());
            throw exception(INVOICE_FILE_DATA_ERROR);
        }

    }






    private InvoiceRecordsDO validateInvoiceExists(Long invoiceId) {

        InvoiceRecordsDO invoiceRecordsDO = invoiceRecordsMapper.selectById(invoiceId);

        if (ObjectUtil.isNull(invoiceRecordsDO)) {
            throw exception(INVOICE_NO_EXIST);
        }

        return invoiceRecordsDO;

    }



    private List<PurchaseRecordDO> validatePurchaseRecordExists(List<Long> purchaseId) {

        List<PurchaseRecordDO> purchaseRecordDOS = purchaseRecordMapper.selectBatchIds(purchaseId);

        if (ObjectUtil.isNull(purchaseRecordDOS)) {
            throw exception(PURCHASE_RECORD_NO_EXIST);
        }

        return purchaseRecordDOS;

    }



}
