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

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.cf.imes.framework.common.exception.ServiceException;
import com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants;
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.LambdaQueryWrapperX;
import com.cf.imes.framework.security.core.LoginUser;
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
import com.cf.imes.module.infra.api.file.FileApi;
import com.cf.imes.module.system.controller.admin.funds.invoice.vo.InvoiceAmountAndAvailablePageReqVO;
import com.cf.imes.module.system.controller.admin.funds.invoice.vo.InvoiceAmountRespVO;
import com.cf.imes.module.system.controller.admin.funds.invoice.vo.InvoiceAvailablePageRespVO;
import com.cf.imes.module.system.controller.admin.funds.invoice.vo.InvoiceConfirmReqVO;
import com.cf.imes.module.system.controller.admin.funds.invoice.vo.InvoiceDetailsPageReqVO;
import com.cf.imes.module.system.controller.admin.funds.invoice.vo.InvoiceDetailsRespVO;
import com.cf.imes.module.system.controller.admin.funds.invoice.vo.InvoicePurchaseRecord;
import com.cf.imes.module.system.controller.admin.funds.invoice.vo.InvoiceRecordsReqVO;
import com.cf.imes.module.system.controller.admin.funds.invoice.vo.InvoiceRecordsRespVO;
import com.cf.imes.module.system.controller.admin.funds.invoice.vo.InvoiceTitleInfo;
import com.cf.imes.module.system.controller.admin.funds.invoice.vo.InvoiceTitleSaveReqVO;
import com.cf.imes.module.system.dal.dataobject.funds.invoice.InvoiceRecordsDO;
import com.cf.imes.module.system.dal.dataobject.funds.invoice.InvoiceTitleInfoDO;
import com.cf.imes.module.system.dal.dataobject.funds.purchase.PurchaseRecordDO;
import com.cf.imes.module.system.dal.dataobject.organ.OrganizationDO;
import com.cf.imes.module.system.dal.mysql.funds.invoice.InvoiceRecordsMapper;
import com.cf.imes.module.system.dal.mysql.funds.invoice.InvoiceTitleInfoMapper;
import com.cf.imes.module.system.dal.mysql.funds.purchase.PurchaseRecordMapper;
import com.cf.imes.module.system.enums.pay.InvoiceIssueTypeEnum;
import com.cf.imes.module.system.enums.pay.InvoiceStatusEnum;
import com.cf.imes.module.system.enums.pay.TradeTypeEnum;
import com.cf.imes.module.system.service.organ.OrganService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import jakarta.annotation.Resource;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;

import static cn.hutool.core.date.DatePattern.PURE_DATETIME_PATTERN;
import static com.cf.imes.framework.common.util.json.JsonUtils.parseObject;
import static com.cf.imes.framework.common.util.json.JsonUtils.toJsonString;
import static com.cf.imes.framework.common.util.json.JsonUtils.unzipString;
import static com.cf.imes.framework.common.util.json.JsonUtils.zipString;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.INVOICE_AMOUNT_ZERO_ERROR;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.INVOICE_APPLY_UNINVOICEABLE_EXIST;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.INVOICE_NO_EXIST;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.INVOICE_TITLE_ENTERPRISE_TAXNUMBER_EMPTY_ERROR;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.INVOICE_TITLE_NOT_EXISTS_ERROR;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.INVOICE_TITLE_ORG_EXISTS_ERROR;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.INVOICE_TITLE_TAXPAYERID_EXISTS_ERROR;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.INVOICING_REJECT_REASON;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.ORG_INVOICE_AGREE_FILE_NULL_ERROR;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.ORG_INVOICE_PURCHASE_RECORD_UPDATE_NUM_NOT_MATCH_ERROR;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.PURCHASE_RECORD_NO_EXIST;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.THIS_INVOICE_IS_FAIL;


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


    @Resource
    private InvoiceTitleInfoMapper invoiceTitleInfoMapper;

    @Resource
    private PurchaseRecordMapper purchaseRecordMapper;

    @Resource
    private InvoiceRecordsMapper invoiceRecordsMapper;

    @Resource
    private OrganService organService;

    @Resource
    private FileApi fileApi;

    @Override
    public InvoiceTitleInfo getInvoiceTitle() {
        InvoiceTitleInfoDO invoiceTitleInfoDO = invoiceTitleInfoMapper.selectOne(new LambdaQueryWrapper<InvoiceTitleInfoDO>()
                .eq(InvoiceTitleInfoDO::getOrganId, SecurityFrameworkUtils.getUserOrganId()));
        return BeanUtils.toBean(invoiceTitleInfoDO, InvoiceTitleInfo.class);
    }

    @Override
    public void insetInvoiceTitle(InvoiceTitleSaveReqVO reqVO) {
        Long organId = SecurityFrameworkUtils.getUserOrganId();

        Long id = reqVO.getId();

        Integer issueType = reqVO.getIssueType();
        // 企业发票抬头校验统一社会信用代码非空
        AssertUtils.notEmpty(InvoiceIssueTypeEnum.ENTERPRISE.getValue().equals(issueType) ? reqVO.getTaxNumber() : "0", INVOICE_TITLE_ENTERPRISE_TAXNUMBER_EMPTY_ERROR);

        // 校验统一社会信用代码和组织id是否唯一
        validOrgAndTaxpayerNo(id, organId, reqVO.getTaxNumber(), issueType);

        if (id != null) {
            validTitleOrg(id, organId);
            // 更新抬头
            InvoiceTitleInfoDO titleInfoDO = BeanUtils.toBean(reqVO, InvoiceTitleInfoDO.class);
            invoiceTitleInfoMapper.updateById(titleInfoDO);
            return;
        }

        // 新增抬头
        invoiceTitleInfoMapper.insert(InvoiceTitleInfoDO.builder()
                .organId(organId)
                .invoiceTitle(reqVO.getInvoiceTitle())
                .invoiceType(reqVO.getInvoiceType())
                .issueType(issueType)
                .taxNumber(reqVO.getTaxNumber())
                .bankName(reqVO.getBankName())
                .bankAccount(reqVO.getBankAccount())
                .registeredAddress(reqVO.getRegisteredAddress())
                .fixedPhone(reqVO.getFixedPhone())
                .email(reqVO.getEmail())
                .build());
    }

    /**
     * 校验组织和统一社会信用代码
     *
     * @param id        发票抬头id
     * @param organId   组织id
     * @param taxNumber 统一社会信用代码
     * @param issueType 开具类型
     */
    private void validOrgAndTaxpayerNo(Long id, Long organId, String taxNumber, Integer issueType) {
        InvoiceTitleInfoDO exist = invoiceTitleInfoMapper.selectOne(new LambdaQueryWrapperX<InvoiceTitleInfoDO>()
                .neIfPresent(InvoiceTitleInfoDO::getId, id)
                .eq(InvoiceTitleInfoDO::getOrganId, organId));

        if (ObjectUtil.isNotNull(exist)) {
            throw new ServiceException(INVOICE_TITLE_ORG_EXISTS_ERROR);
        }
        exist = invoiceTitleInfoMapper.selectOne(new LambdaQueryWrapperX<InvoiceTitleInfoDO>()
                .neIfPresent(InvoiceTitleInfoDO::getId, id)
                .eq(InvoiceTitleInfoDO::getTaxNumber, taxNumber));
        // 开具企业类型发票校验统一社会信用代码
        if (InvoiceIssueTypeEnum.ENTERPRISE.getValue().equals(issueType) && ObjectUtil.isNotNull(exist)) {
            throw new ServiceException(INVOICE_TITLE_TAXPAYERID_EXISTS_ERROR);
        }
    }

    /**
     * 校验抬头组织
     *
     * @param id
     * @param organId
     */
    private InvoiceTitleInfoDO validTitleOrg(Long id, Long organId) {
        InvoiceTitleInfoDO titleInfoDO = invoiceTitleInfoMapper.selectById(id);
        // 抬头不存在
        AssertUtils.notEmpty(titleInfoDO, INVOICE_TITLE_NOT_EXISTS_ERROR);
        // 抬头组织不匹配
        AssertUtils.notEquals(titleInfoDO.getOrganId(), organId, INVOICE_TITLE_NOT_EXISTS_ERROR);
        return titleInfoDO;
    }

    @Override
    public InvoiceAmountRespVO getInvoiceAmount() {
        return purchaseRecordMapper.getOrgInvoiceAmountStatistics(SecurityFrameworkUtils.getUserOrganId());
    }

    @Override
    public InvoiceAmountRespVO getManageInvoiceAmount() {
        return invoiceRecordsMapper.getManageInvoiceAmountStatistics();
    }

    @Override
    public PageResult<InvoiceAvailablePageRespVO> getAvailablePage(InvoiceAmountAndAvailablePageReqVO invoiceAmountAndAvailablePageReqVO) {
        // 生成起时间0点和止时间12点的区间
        invoiceAmountAndAvailablePageReqVO.setCreateTime(LocalDateTimeUtils.generateTimeSection(invoiceAmountAndAvailablePageReqVO.getCreateTime()));

        PageResult<PurchaseRecordDO> availablePage = purchaseRecordMapper.selectPage(invoiceAmountAndAvailablePageReqVO);
        if (CollUtil.isEmpty(availablePage.getList())) {
            return new PageResult<>(availablePage.getTotal());
        }

        List<InvoiceAvailablePageRespVO> availablePageRespVOS = new ArrayList<>();
        availablePage.getList().forEach(i -> {
            InvoiceAvailablePageRespVO availablePageRespVO = new InvoiceAvailablePageRespVO();
            availablePageRespVO.setId(i.getId());
            availablePageRespVO.setCreateTime(i.getCreateTime());
            availablePageRespVO.setTradeType(TradeTypeEnum.PRODUCT.getCode());
            // 可开票金额=余额支付+扫码支付
            availablePageRespVO.setAvailableAmount((i.getExternalSpent()));
            availablePageRespVO.setProductName(i.getProductName());
            availablePageRespVO.setTotalAmount(i.getTotalAmount());
            availablePageRespVOS.add(availablePageRespVO);
        });
        return new PageResult<>(availablePageRespVOS, availablePage.getTotal());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void applyInvoice(InvoiceRecordsReqVO reqVO) {
        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
        if (loginUser == null) {
            throw new ServiceException(GlobalErrorCodeConstants.UNAUTHORIZED);
        }
        Long organId = loginUser.getOrganId();

        List<Long> purchaseRecordId = reqVO.getPurchaseRecordId();
        // 查询发票抬头
        InvoiceTitleInfoDO invoiceTitleInfoDO = invoiceTitleInfoMapper.selectOne(new LambdaQueryWrapper<InvoiceTitleInfoDO>()
                .eq(InvoiceTitleInfoDO::getOrganId, organId));

        AssertUtils.notEmpty(invoiceTitleInfoDO, INVOICE_TITLE_NOT_EXISTS_ERROR);

        // 检查关联记录的购买记录，不能有已开票的
        List<PurchaseRecordDO> purchaseRecordDOS = purchaseRecordMapper.selectByIds(purchaseRecordId);
        boolean uninvoicableExist = purchaseRecordDOS.stream().filter(p -> !InvoiceStatusEnum.INVOICABLE.getCode().equals(p.getIsInvocing())).findAny().isPresent();
        AssertUtils.isTrue(uninvoicableExist, INVOICE_APPLY_UNINVOICEABLE_EXIST);

        // 查询可开票金额：余额支付+扫码支付
        BigDecimal invoicableAmount = purchaseRecordMapper.getInvoicableAmount(organId, purchaseRecordId);
        // 可开票金额不能为0
        AssertUtils.isTrue(invoicableAmount.compareTo(BigDecimal.ZERO) == 0, INVOICE_AMOUNT_ZERO_ERROR);

        // 更新购买记录表的发票状态
        int update = purchaseRecordMapper.update(new LambdaUpdateWrapper<PurchaseRecordDO>()
                .eq(PurchaseRecordDO::getOrganId, organId)
                .eq(PurchaseRecordDO::getDeleted, false)
                .in(PurchaseRecordDO::getId, purchaseRecordId)
                .set(PurchaseRecordDO::getIsInvocing, InvoiceStatusEnum.PENDINGINVOICING.getCode()));
        if (update != purchaseRecordId.size()) {
            throw new ServiceException(ORG_INVOICE_PURCHASE_RECORD_UPDATE_NUM_NOT_MATCH_ERROR);
        }

        // 查询组织名称
        OrganizationDO organizationDO = organService.validOrgan(organId);

        // 插入开票记录
        invoiceRecordsMapper.insert(InvoiceRecordsDO.builder()
                .userId(loginUser.getId())
                .organName(organizationDO.getName())
                .organId(organId)
                .purchaseIds(JsonUtils.toJsonString(purchaseRecordId))
                .invoiceAmount(invoicableAmount)
                .invoiceTitleId(invoiceTitleInfoDO.getId())
                .invoiceTitleInfo(zipString(toJsonString(invoiceTitleInfoDO)))
                .status(InvoiceStatusEnum.PENDINGINVOICING.getCode())
                .build());
    }

    @Override
    public PageResult<InvoiceRecordsRespVO> getInvoiceRecords(InvoiceDetailsPageReqVO pageReqVO) {
        // 时间类型规范为开始0点，结束12点
        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 InvoiceDetailsRespVO getInvoiceRecordDetail(Long recordId) {
        InvoiceRecordsDO invoiceRecordsDO = invoiceRecordsMapper.selectById(recordId);
        AssertUtils.notEmpty(invoiceRecordsDO, INVOICE_NO_EXIST);

        // 记录中的购买记录id数组转list
        String purchaseIds = Optional.ofNullable(invoiceRecordsDO.getPurchaseIds()).orElse("[]");
        List<Long> purchaseIdList = JSON.parseArray(purchaseIds).toJavaList(Long.class).stream().distinct().toList();

        // 查询关联的购买记录
        List<PurchaseRecordDO> purchaseRecordDOS = purchaseRecordMapper.selectList(new LambdaQueryWrapper<PurchaseRecordDO>()
                .eq(PurchaseRecordDO::getOrganId, invoiceRecordsDO.getOrganId())
                .in(PurchaseRecordDO::getId, purchaseIdList));

        List<InvoicePurchaseRecord> invoicePurchaseRecords = new ArrayList<>();
        for (PurchaseRecordDO purchaseRecordDO : purchaseRecordDOS) {
            InvoicePurchaseRecord invoicePurchaseRecord = new InvoicePurchaseRecord();
            invoicePurchaseRecord.setPurchaseRecordId(purchaseRecordDO.getId());
            invoicePurchaseRecord.setProductName(purchaseRecordDO.getProductName());
            invoicePurchaseRecord.setTradeType(TradeTypeEnum.PRODUCT.getCode());
            invoicePurchaseRecord.setInvoicableAmount(purchaseRecordDO.getTotalAmount());
            invoicePurchaseRecord.setTotalAmount(purchaseRecordDO.getTotalAmount());
            invoicePurchaseRecord.setCreateTime(purchaseRecordDO.getCreateTime());
            invoicePurchaseRecords.add(invoicePurchaseRecord);
        }

        // 解压发票抬头
        InvoiceTitleInfo invoiceTitleInfo = parseObject(unzipString(invoiceRecordsDO.getInvoiceTitleInfo()), InvoiceTitleInfo.class);

        return InvoiceDetailsRespVO.builder()
                .invoicePurchaseRecords(invoicePurchaseRecords)
                .invoiceNo(invoiceRecordsDO.getInvoiceNo())
                .invoiceTitleInfo(invoiceTitleInfo)
                .applicant(invoiceRecordsDO.getCreator())
                .orgName(invoiceRecordsDO.getOrganName())
                .applyTime(invoiceRecordsDO.getCreateTime())
                .invoiceAttachmentId(invoiceRecordsDO.getInvoiceAttachmentId())
                .invoiceAttachmentName(invoiceRecordsDO.getInvoiceAttachmentName())
                .remark(invoiceRecordsDO.getRemark())
                .build();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void confirmInvoice(InvoiceConfirmReqVO reqVO, MultipartFile file) throws IOException {
        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
        if (loginUser == null) {
            throw new ServiceException(GlobalErrorCodeConstants.UNAUTHORIZED);
        }

        // 校验发票申请记录是否存在
        InvoiceRecordsDO invoiceRecordsDO = validateInvoiceExists(reqVO.getId());

        if (invoiceRecordsDO.getStatus().equals(InvoiceStatusEnum.INVOICINGFAILED.getCode())
                || invoiceRecordsDO.getStatus().equals(InvoiceStatusEnum.INVOICINGSUCCESSFUL.getCode())) {
            throw new ServiceException(THIS_INVOICE_IS_FAIL);
        }

        // 校验发票记录下的购买记录是否存在
        String purchaseIds = Optional.ofNullable(invoiceRecordsDO.getPurchaseIds()).orElse("[]");
        List<Long> purchaseIdList = JSON.parseArray(purchaseIds).toJavaList(Long.class).stream().distinct().toList();
        List<PurchaseRecordDO> purchaseRecordDOS = validatePurchaseRecordExists(purchaseIdList);

        // 获取对应组织的抬头信息
        InvoiceTitleInfoDO invoiceTitleInfoDO = validTitleOrg(invoiceRecordsDO.getInvoiceTitleId(), invoiceRecordsDO.getOrganId());

        LocalDateTime now = LocalDateTime.now();
        if (reqVO.isAgree()) {
            // 同意开票
            // 更新发票申请记录状态
            invoiceRecordsDO.setStatus(InvoiceStatusEnum.INVOICINGSUCCESSFUL.getCode());
            invoiceRecordsDO.setRemark(reqVO.getRemark());
            // 更新购买记录状态
            purchaseRecordDOS.forEach(f -> f.setIsInvocing(InvoiceStatusEnum.INVOICINGSUCCESSFUL.getCode()));

            //  发票文件入库
            if (ObjectUtil.isNull(file) || file.isEmpty()) {
                throw new ServiceException(ORG_INVOICE_AGREE_FILE_NULL_ERROR);
            }

            String invoiceFileName = invoiceTitleInfoDO.getInvoiceTitle() + "-" + LocalDateTimeUtil.format(now, PURE_DATETIME_PATTERN) + "-" + invoiceRecordsDO.getInvoiceAmount().stripTrailingZeros().toPlainString() + getFileExtension(file.getOriginalFilename());
            Long fileId = fileApi.createFileAndReturnId(IoUtil.readBytes(file.getInputStream()), invoiceFileName);
            invoiceRecordsDO.setInvoiceAttachmentId(fileId);
            invoiceRecordsDO.setInvoiceAttachmentName(invoiceFileName);
            invoiceRecordsDO.setInvoiceNo(reqVO.getInvoiceNo());
            invoiceRecordsDO.setInvoiceTime(now);
        } else {
            // 拒绝开票
            // 更新发票申请记录状态
            invoiceRecordsDO.setStatus(InvoiceStatusEnum.INVOICINGFAILED.getCode());
            String remark = Optional.ofNullable(reqVO.getRemark()).orElseThrow(() -> new ServiceException(INVOICING_REJECT_REASON));
            invoiceRecordsDO.setRemark(remark);
            invoiceRecordsDO.setInvoiceTime(now);

            // 更新购买记录状态
            purchaseRecordDOS.forEach(f -> f.setIsInvocing(InvoiceStatusEnum.INVOICABLE.getCode()));

        }
        // 更新开票人信息
        invoiceRecordsDO.setInvoicePerson(loginUser.getNickname());
        invoiceRecordsDO.setInvoicePersonId(loginUser.getId());

        invoiceRecordsMapper.updateById(invoiceRecordsDO);

        purchaseRecordMapper.updateBatch(purchaseRecordDOS);
    }

    /**
     * 截取文件类型
     *
     * @param fileName
     * @return
     */
    public static String getFileExtension(String fileName) {
        if (StringUtils.isEmpty(fileName)) {
            return "";
        }

        int lastDotIndex = fileName.lastIndexOf('.');
        if (lastDotIndex < 0 || lastDotIndex == fileName.length() - 1) {
            // 没有点号，或者点号在最后一位（例如 "file."）
            return "";
        }

        // 截取后缀部分并转小写
        return "." + fileName.substring(lastDotIndex + 1).toLowerCase(Locale.ROOT);
    }

    /**
     * 校验发票申请记录
     *
     * @param invoiceId
     * @return
     */
    private InvoiceRecordsDO validateInvoiceExists(Long invoiceId) {
        InvoiceRecordsDO invoiceRecordsDO = invoiceRecordsMapper.selectById(invoiceId);
        if (ObjectUtil.isNull(invoiceRecordsDO)) {
            throw new ServiceException(INVOICE_NO_EXIST);
        }
        return invoiceRecordsDO;
    }

    /**
     * 校验发票记录下的购买记录
     *
     * @param purchaseId
     * @return
     */
    private List<PurchaseRecordDO> validatePurchaseRecordExists(List<Long> purchaseId) {
        List<PurchaseRecordDO> purchaseRecordDOS = purchaseRecordMapper.selectByIds(purchaseId);
        if (CollUtil.isEmpty(purchaseRecordDOS)) {
            throw new ServiceException(PURCHASE_RECORD_NO_EXIST);
        }
        return purchaseRecordDOS;
    }
}
