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

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.cf.imes.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.cf.imes.framework.common.pojo.PageResult;
import com.cf.imes.framework.security.core.LoginUser;
import com.cf.imes.framework.security.core.util.SecurityFrameworkUtils;
import com.cf.imes.framework.security.test.WithMockLoginUser;
import com.cf.imes.framework.test.core.ut.BaseMockitoUnitTest;
import com.cf.imes.module.infra.api.file.FileApi;
import com.cf.imes.module.system.common.WithMockLoginUserExtension;
import com.cf.imes.module.system.controller.admin.funds.invoice.vo.*;
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.PayChannelCodeEnum;
import com.cf.imes.module.system.enums.pay.TradeTypeEnum;
import com.cf.imes.module.system.service.organ.OrganService;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.mock.web.MockMultipartFile;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;

import static com.cf.imes.framework.common.util.json.JsonUtils.zipString;
import static com.cf.imes.framework.test.core.util.AssertUtils.assertServiceException;
import static com.cf.imes.framework.test.core.util.RandomUtils.randomPojo;
import static com.cf.imes.module.system.enums.ErrorCodeConstants.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * 发票服务测试类
 *
 * @author Gqr
 * @since 2025/12/15 15:31
 */
@ExtendWith(WithMockLoginUserExtension.class)
class InvoiceServiceImplTest extends BaseMockitoUnitTest {

    @InjectMocks
    private InvoiceServiceImpl invoiceService;

    @Mock
    private InvoiceTitleInfoMapper invoiceTitleInfoMapper;

    @Mock
    private PurchaseRecordMapper purchaseRecordMapper;

    @Mock
    private InvoiceRecordsMapper invoiceRecordsMapper;

    @Mock
    private OrganService organService;

    @Mock
    private FileApi fileApi;

    @BeforeAll
    static void init(){
        TableInfoHelper.initTableInfo(new MapperBuilderAssistant(new MybatisConfiguration(), ""), PurchaseRecordDO.class);
    }

    @Test
    @WithMockLoginUser
    void testGetInvoiceTitle() {
        Long organId = SecurityFrameworkUtils.getUserOrganId();

        // 使用真实数据查询发票抬头信息
        InvoiceTitleInfoDO invoiceTitleInfoDO = randomPojo(InvoiceTitleInfoDO.class, o -> {
            o.setId(1L);
            o.setOrganId(organId);
            o.setInvoiceTitle("测试公司");
        });
        when(invoiceTitleInfoMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(invoiceTitleInfoDO);

        // 调用
        InvoiceTitleInfo result = invoiceService.getInvoiceTitle();

        // 断言
        assertNotNull(result);
        assertEquals(1L, result.getId());
        assertEquals("测试公司", result.getInvoiceTitle());
        assertEquals(organId, result.getOrganId());

    }

    @Test
    @WithMockLoginUser
    void insetInvoiceTitle_create_success() {
        Long organId = SecurityFrameworkUtils.getUserOrganId();

        InvoiceTitleSaveReqVO reqVO = new InvoiceTitleSaveReqVO();
        reqVO.setInvoiceTitle("测试公司");
        reqVO.setIssueType(InvoiceIssueTypeEnum.ENTERPRISE.getValue());
        reqVO.setInvoiceType(0);
        reqVO.setTaxNumber("91330101MA2HXXXXXX");
        reqVO.setEmail("test@company.com");

        // 内部校验
        when(invoiceTitleInfoMapper.selectOne(any(LambdaQueryWrapper.class))).thenReturn(null);

        invoiceService.insetInvoiceTitle(reqVO);

        // 不走更新
        verify(invoiceTitleInfoMapper, never()).updateById(any(InvoiceTitleInfoDO.class));

        // 校验新增
        ArgumentCaptor<InvoiceTitleInfoDO> captor =
                ArgumentCaptor.forClass(InvoiceTitleInfoDO.class);

        verify(invoiceTitleInfoMapper).insert(captor.capture());

        InvoiceTitleInfoDO inserted = captor.getValue();

        assertEquals(organId, inserted.getOrganId());
        assertEquals(reqVO.getInvoiceTitle(), inserted.getInvoiceTitle());
        assertEquals(reqVO.getInvoiceType(), inserted.getInvoiceType());
        assertEquals(reqVO.getIssueType(), inserted.getIssueType());
        assertEquals(reqVO.getTaxNumber(), inserted.getTaxNumber());
        assertEquals(reqVO.getBankName(), inserted.getBankName());
        assertEquals(reqVO.getBankAccount(), inserted.getBankAccount());
        assertEquals(reqVO.getRegisteredAddress(), inserted.getRegisteredAddress());
        assertEquals(reqVO.getFixedPhone(), inserted.getFixedPhone());
        assertEquals(reqVO.getEmail(), inserted.getEmail());
    }

    @Test
    @WithMockLoginUser
    void insetInvoiceTitle_enterpriseWithoutTaxNumber_throwException() {
        InvoiceTitleSaveReqVO reqVO = new InvoiceTitleSaveReqVO();
        reqVO.setInvoiceTitle("测试公司");
        reqVO.setIssueType(InvoiceIssueTypeEnum.ENTERPRISE.getValue());
        reqVO.setInvoiceType(0);
        reqVO.setTaxNumber(null);

        assertServiceException(() -> invoiceService.insetInvoiceTitle(reqVO), INVOICE_TITLE_ENTERPRISE_TAXNUMBER_EMPTY_ERROR);

        // 校验未调用
        verify(invoiceTitleInfoMapper, never()).selectOne(any(LambdaQueryWrapper.class));

    }

    @Test
    @WithMockLoginUser
    void insetInvoiceTitle_sameOrgan_throwOrgExists() {
        Long organId = SecurityFrameworkUtils.getUserOrganId();

        when(invoiceTitleInfoMapper.selectOne(any())).thenReturn(InvoiceTitleInfoDO.builder()
                .organId(organId)
                .issueType(InvoiceIssueTypeEnum.PERSONAL.getValue())
                .invoiceTitle("已存在")
                .build());

        InvoiceTitleSaveReqVO reqVO = new InvoiceTitleSaveReqVO();
        reqVO.setInvoiceTitle("新抬头");
        reqVO.setIssueType(InvoiceIssueTypeEnum.PERSONAL.getValue());

        assertServiceException(() -> invoiceService.insetInvoiceTitle(reqVO), INVOICE_TITLE_ORG_EXISTS_ERROR);
    }

    @Test
    @WithMockLoginUser
    void insetInvoiceTitle_update_notExists_throwException() {
        InvoiceTitleSaveReqVO reqVO = new InvoiceTitleSaveReqVO();
        reqVO.setId(999L); // 数据库不存在
        reqVO.setInvoiceTitle("测试抬头");
        reqVO.setIssueType(InvoiceIssueTypeEnum.PERSONAL.getValue());

        assertServiceException(() -> invoiceService.insetInvoiceTitle(reqVO), INVOICE_TITLE_NOT_EXISTS_ERROR);
    }

    @Test
    @WithMockLoginUser
    void insetInvoiceTitle_update_orgNotMatch_throwException() {
        InvoiceTitleInfoDO exist = InvoiceTitleInfoDO.builder()
                .id(100L)
                .organId(2L)
                .issueType(InvoiceIssueTypeEnum.PERSONAL.getValue())
                .invoiceTitle("原抬头")
                .build();
        when(invoiceTitleInfoMapper.selectById(any())).thenReturn(exist);

        InvoiceTitleSaveReqVO reqVO = new InvoiceTitleSaveReqVO();
        reqVO.setId(exist.getId());
        reqVO.setInvoiceTitle("非法更新");
        reqVO.setIssueType(exist.getIssueType());

        assertServiceException(() -> invoiceService.insetInvoiceTitle(reqVO), INVOICE_TITLE_NOT_EXISTS_ERROR);
    }

    @Test
    @WithMockLoginUser
    void insetInvoiceTitle_insert_success() {
        Long organId = SecurityFrameworkUtils.getUserOrganId();

        InvoiceTitleSaveReqVO reqVO = new InvoiceTitleSaveReqVO();
        reqVO.setInvoiceTitle("新增抬头");
        reqVO.setIssueType(InvoiceIssueTypeEnum.PERSONAL.getValue());
        reqVO.setInvoiceType(0);
        reqVO.setEmail("test@test.com");

        invoiceService.insetInvoiceTitle(reqVO);

        // 验证插入
        verify(invoiceTitleInfoMapper).insert(argThat((InvoiceTitleInfoDO infoDO) ->
                infoDO.getInvoiceTitle().equals(reqVO.getInvoiceTitle())
                        && infoDO.getOrganId().equals(organId)
                        && infoDO.getIssueType().equals(reqVO.getIssueType())
                        && infoDO.getInvoiceType().equals(reqVO.getInvoiceType())
                        && infoDO.getEmail().equals(reqVO.getEmail())
        ));
    }

    @Test
    @WithMockLoginUser
    void insetInvoiceTitle_enterprise_taxNumberExists_throwException() {

        when(invoiceTitleInfoMapper.selectOne(any())).thenReturn(null).thenReturn(InvoiceTitleInfoDO.builder()
                .organId(2L) // 不同组织也没关系
                .invoiceTitle("已存在抬头")
                .issueType(InvoiceIssueTypeEnum.ENTERPRISE.getValue())
                .taxNumber("91330100XXXXXX")
                .build());

        // 再新增一条相同税号
        InvoiceTitleSaveReqVO reqVO = new InvoiceTitleSaveReqVO();
        reqVO.setInvoiceTitle("新抬头");
        reqVO.setIssueType(InvoiceIssueTypeEnum.ENTERPRISE.getValue());
        reqVO.setTaxNumber("91330100XXXXXX");

        assertServiceException(() -> invoiceService.insetInvoiceTitle(reqVO), INVOICE_TITLE_TAXPAYERID_EXISTS_ERROR);
    }

    @Test
    @WithMockLoginUser
    void insetInvoiceTitle_update_success() {
        Long organId = SecurityFrameworkUtils.getUserOrganId();

        InvoiceTitleInfoDO exist = InvoiceTitleInfoDO.builder()
                .id(100L)
                .organId(organId)
                .invoiceTitle("旧抬头")
                .issueType(InvoiceIssueTypeEnum.PERSONAL.getValue())
                .build();
        when(invoiceTitleInfoMapper.selectById(exist.getId())).thenReturn(exist);

        // when：更新
        InvoiceTitleSaveReqVO reqVO = new InvoiceTitleSaveReqVO();
        reqVO.setId(exist.getId());
        reqVO.setInvoiceTitle("新抬头");
        reqVO.setIssueType(InvoiceIssueTypeEnum.PERSONAL.getValue());

        invoiceService.insetInvoiceTitle(reqVO);

        // 捕获 update 的参数，断言字段
        ArgumentCaptor<InvoiceTitleInfoDO> captor =
                ArgumentCaptor.forClass(InvoiceTitleInfoDO.class);
        verify(invoiceTitleInfoMapper).updateById(captor.capture());

        InvoiceTitleInfoDO actual = captor.getValue();

        assertEquals(reqVO.getId(), actual.getId());
        assertEquals(reqVO.getInvoiceTitle(), actual.getInvoiceTitle());
        assertEquals(reqVO.getIssueType(), actual.getIssueType());

    }

    @Test
    void getAvailablePage_whenEmptyList_returnEmptyPageResult() {
        // given
        InvoiceAmountAndAvailablePageReqVO reqVO = new InvoiceAmountAndAvailablePageReqVO();

        when(purchaseRecordMapper.selectPage(any(InvoiceAmountAndAvailablePageReqVO.class))).thenReturn(new PageResult());

        // when
        PageResult<InvoiceAvailablePageRespVO> result =
                invoiceService.getAvailablePage(reqVO);

        // then
        assertNotNull(result);
        assertEquals(0, result.getTotal());
        assertTrue(CollUtil.isEmpty(result.getList()));
    }

    @Test
    void getAvailablePage_whenHasData_returnConvertedPage() {
        // given
        InvoiceAmountAndAvailablePageReqVO reqVO =
                new InvoiceAmountAndAvailablePageReqVO();

        PurchaseRecordDO invoiceRecordsDO = randomPojo(PurchaseRecordDO.class, o -> {
            o.setId(1L);
            o.setCreateTime(LocalDateTime.now());
            o.setExternalSpent(new BigDecimal("100.00"));
            o.setProductName("测试产品");
            o.setTotalAmount(new BigDecimal("200.00"));
            o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
            o.setIsInvocing(InvoiceStatusEnum.INVOICABLE.getCode());
            o.setInitial(false);
            o.setOrganId(1L);
        });
        when(purchaseRecordMapper.selectPage(any(InvoiceAmountAndAvailablePageReqVO.class))).thenReturn(new PageResult(List.of(invoiceRecordsDO), 1L));

        // when
        PageResult<InvoiceAvailablePageRespVO> result =
                invoiceService.getAvailablePage(reqVO);

        // then
        assertNotNull(result);
        assertEquals(1L, result.getTotal());
        assertEquals(1, result.getList().size());

        InvoiceAvailablePageRespVO vo = result.getList().get(0);
        assertEquals(1L, vo.getId());
        assertEquals(TradeTypeEnum.PRODUCT.getCode(), vo.getTradeType());
        assertEquals(invoiceRecordsDO.getExternalSpent(), vo.getAvailableAmount());
        assertEquals("测试产品", vo.getProductName());
        assertEquals(invoiceRecordsDO.getTotalAmount(), vo.getTotalAmount());
    }

    @Test
    void applyInvoice_whenNoLogin_throwUnauthorized() {
        InvoiceRecordsReqVO reqVO = new InvoiceRecordsReqVO();

        assertServiceException(() -> invoiceService.applyInvoice(reqVO), GlobalErrorCodeConstants.UNAUTHORIZED);
    }

    @Test
    @WithMockLoginUser(organId = 1L)
    void applyInvoice_whenUninvoicableExist_throwException() {
        InvoiceRecordsReqVO reqVO = new InvoiceRecordsReqVO();
        assertServiceException(() -> invoiceService.applyInvoice(reqVO), INVOICE_TITLE_NOT_EXISTS_ERROR);
    }

    @Test
    @WithMockLoginUser(organId = 1L)
    void applyInvoice_uninvoiceableExist() {
        // 先插入一条企业发票抬头
        InvoiceTitleInfoDO exist = InvoiceTitleInfoDO.builder()
                .organId(1L) // 不同组织也没关系
                .invoiceTitle("已存在抬头")
                .issueType(InvoiceIssueTypeEnum.ENTERPRISE.getValue())
                .taxNumber("91330100XXXXXX")
                .build();
        when(invoiceTitleInfoMapper.selectOne(any())).thenReturn(exist);

        InvoiceRecordsReqVO reqVO = new InvoiceRecordsReqVO();

        PurchaseRecordDO invoiceRecordsDO = randomPojo(PurchaseRecordDO.class, o -> {
            o.setId(1L);
            o.setCreateTime(LocalDateTime.now());
            o.setExternalSpent(new BigDecimal("100.00"));
            o.setProductName("测试产品");
            o.setTotalAmount(new BigDecimal("200.00"));
            o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
            o.setIsInvocing(InvoiceStatusEnum.INVOICINGSUCCESSFUL.getCode());
            o.setInitial(false);
            o.setOrganId(1L);
        });
        when(purchaseRecordMapper.selectByIds(anyList())).thenReturn(List.of(invoiceRecordsDO));

        // 有不可开票状态的记录
        reqVO.setPurchaseRecordId(List.of(invoiceRecordsDO.getId()));
        assertServiceException(() -> invoiceService.applyInvoice(reqVO), INVOICE_APPLY_UNINVOICEABLE_EXIST);

        // 可开金额为0
        invoiceRecordsDO.setExternalSpent(BigDecimal.ZERO);
        invoiceRecordsDO.setIsInvocing(InvoiceStatusEnum.INVOICABLE.getCode());
        when(purchaseRecordMapper.selectByIds(anyList())).thenReturn(List.of(invoiceRecordsDO));

        reqVO.setPurchaseRecordId(List.of(invoiceRecordsDO.getId()));
        when(purchaseRecordMapper.getInvoicableAmount(anyLong(), anyList())).thenReturn(BigDecimal.ZERO);

        assertServiceException(() -> invoiceService.applyInvoice(reqVO), INVOICE_AMOUNT_ZERO_ERROR);

        // 更新开票状态和开票id数量不匹配
        invoiceRecordsDO.setIsInvocing(InvoiceStatusEnum.INVOICABLE.getCode());
        invoiceRecordsDO.setExternalSpent(new BigDecimal("100"));
        when(purchaseRecordMapper.selectByIds(anyList())).thenReturn(List.of(invoiceRecordsDO));
        when(purchaseRecordMapper.getInvoicableAmount(anyLong(), anyList())).thenReturn(BigDecimal.ONE);
        when(purchaseRecordMapper.update(any(LambdaUpdateWrapper.class))).thenReturn(10);

        reqVO.setPurchaseRecordId(List.of(invoiceRecordsDO.getId(), 999L));
        assertServiceException(
                () -> invoiceService.applyInvoice(reqVO),
                ORG_INVOICE_PURCHASE_RECORD_UPDATE_NUM_NOT_MATCH_ERROR
        );
    }


    @Test
    @WithMockLoginUser(organId = 1L)
    void applyInvoice_success() {
        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
        Long organId = loginUser.getOrganId();
        Long userId = loginUser.getId();

        // 先插入一条企业发票抬头
        InvoiceTitleInfoDO exist = InvoiceTitleInfoDO.builder()
                .organId(organId) // 不同组织也没关系
                .invoiceTitle("已存在抬头")
                .issueType(InvoiceIssueTypeEnum.ENTERPRISE.getValue())
                .taxNumber("91330100XXXXXX")
                .build();
        when(invoiceTitleInfoMapper.selectOne(any())).thenReturn(exist);

        InvoiceRecordsReqVO reqVO = new InvoiceRecordsReqVO();

        PurchaseRecordDO invoiceRecordsDO = randomPojo(PurchaseRecordDO.class, o -> {
            o.setCreateTime(LocalDateTime.now());
            o.setExternalSpent(new BigDecimal("100.00"));
            o.setProductName("测试产品");
            o.setTotalAmount(new BigDecimal("200.00"));
            o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
            o.setIsInvocing(InvoiceStatusEnum.INVOICABLE.getCode());
            o.setInitial(false);
            o.setOrganId(organId);
        });
        when(purchaseRecordMapper.selectByIds(anyList())).thenReturn(List.of(invoiceRecordsDO));
        when(purchaseRecordMapper.getInvoicableAmount(anyLong(), anyList())).thenReturn(BigDecimal.ONE);
        when(purchaseRecordMapper.update(any(LambdaUpdateWrapper.class))).thenReturn(1);

        Long recordId = invoiceRecordsDO.getId();
        reqVO.setPurchaseRecordId(List.of(recordId));

        // 组织
        OrganizationDO org = new OrganizationDO();
        org.setName("测试组织");
        org.setId(organId);
        when(organService.validOrgan(1L)).thenReturn(org);

        assertDoesNotThrow(() -> {
            invoiceService.applyInvoice(reqVO);
        });

        // 验证插入
        verify(invoiceRecordsMapper).insert(argThat((InvoiceRecordsDO recordsDO) ->
                recordsDO.getOrganId().equals(organId)
                        && recordsDO.getUserId().equals(userId)
                        && recordsDO.getOrganName().equals("测试组织")
                        && recordsDO.getInvoiceAmount().equals(BigDecimal.ONE)
                        && recordsDO.getStatus().equals(InvoiceStatusEnum.PENDINGINVOICING.getCode())
        ));
    }

    @Test
    void getInvoiceRecords_whenEmptyList_returnEmptyPageResult() {
        // given
        InvoiceDetailsPageReqVO pageReqVO = new InvoiceDetailsPageReqVO();

        // when
        when(invoiceRecordsMapper.selectInvoiceRecordsPage(any())).thenReturn(new PageResult<>());
        PageResult<InvoiceRecordsRespVO> result = invoiceService.getInvoiceRecords(pageReqVO);

        // then
        assertNotNull(result);
        assertEquals(0, result.getTotal());
        assertTrue(CollUtil.isEmpty(result.getList()));
    }

    @Test
    void getInvoiceRecords_whenHasData_returnConvertedPage() {
        // given
        InvoiceDetailsPageReqVO pageReqVO = new InvoiceDetailsPageReqVO();

        InvoiceRecordsDO invoiceRecordsDO = randomPojo(InvoiceRecordsDO.class, o->{
            o.setId(1L);
            o.setInvoiceAmount(new BigDecimal("100.00"));
            o.setStatus(InvoiceStatusEnum.INVOICABLE.getCode());
        });
        when(invoiceRecordsMapper.selectInvoiceRecordsPage(any())).thenReturn(new PageResult<>(List.of(invoiceRecordsDO), 1L));

        // when
        PageResult<InvoiceRecordsRespVO> result = invoiceService.getInvoiceRecords(pageReqVO);

        // then
        assertNotNull(result);
        assertEquals(1L, result.getTotal());
        assertEquals(1, result.getList().size());

        InvoiceRecordsRespVO vo = result.getList().get(0);
        assertEquals(1L, vo.getId());
        assertEquals(invoiceRecordsDO.getInvoiceAmount(), vo.getInvoiceAmount());
        assertEquals(invoiceRecordsDO.getStatus(), vo.getStatus());
    }

    @Test
    void getInvoiceRecordDetail_whenRecordNotExists_thenThrowException() {
        assertServiceException(() -> invoiceService.getInvoiceRecordDetail(1L), INVOICE_NO_EXIST);
    }

    @Test
    void getInvoiceRecordDetail_whenRecordExists_thenReturnDetail() {
        // 准备数据
        InvoiceRecordsDO invoiceRecordsDO = randomPojo(InvoiceRecordsDO.class, o -> {
            o.setId(1L);
            o.setOrganId(1L);
            o.setInvoiceAmount(new BigDecimal("100.00"));
            o.setPurchaseIds("[1, 2]");
            o.setInvoiceTitleInfo(zipString("{\"invoiceTitle\":\"测试公司\"}"));
        });
        when(invoiceRecordsMapper.selectById(anyLong())).thenReturn(invoiceRecordsDO);

        PurchaseRecordDO purchase1 = randomPojo(PurchaseRecordDO.class, o -> {
            o.setId(1L);
            o.setOrganId(1L);
            o.setProductName("产品1");
            o.setTotalAmount(new BigDecimal("50.00"));
            o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
            o.setIsInvocing(InvoiceStatusEnum.INVOICABLE.getCode());
            o.setInitial(false);
        });

        PurchaseRecordDO purchase2 = randomPojo(PurchaseRecordDO.class, o -> {
            o.setId(2L);
            o.setOrganId(1L);
            o.setProductName("产品2");
            o.setTotalAmount(new BigDecimal("50.00"));
            o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
            o.setIsInvocing(InvoiceStatusEnum.INVOICABLE.getCode());
            o.setInitial(false);
        });
        when(purchaseRecordMapper.selectList(any(LambdaQueryWrapper.class))).thenReturn(List.of(purchase1, purchase2));

        // 调用方法
        InvoiceDetailsRespVO result = invoiceService.getInvoiceRecordDetail(1L);

        // 验证结果
        assertNotNull(result);
        assertEquals(2, result.getInvoicePurchaseRecords().size());
        assertEquals("测试公司", result.getInvoiceTitleInfo().getInvoiceTitle());
    }

    @Test
    void confirmInvoice_whenNoLogin_thenThrowUnauthorized() {
        InvoiceConfirmReqVO reqVO = new InvoiceConfirmReqVO();
        assertServiceException(() -> invoiceService.confirmInvoice(reqVO, null), GlobalErrorCodeConstants.UNAUTHORIZED);
    }

    @Test
    @WithMockLoginUser
    void confirmInvoice_whenRecordNotExists_thenThrowException() {
        InvoiceConfirmReqVO reqVO = new InvoiceConfirmReqVO();
        reqVO.setId(1L);
        assertServiceException(() -> invoiceService.confirmInvoice(reqVO, null), INVOICE_NO_EXIST);
    }

    @Test
    @WithMockLoginUser
    void confirmInvoice_whenRecordAlreadySuccess_thenThrowException() {
        // 准备数据
        InvoiceRecordsDO invoiceRecordsDO = randomPojo(InvoiceRecordsDO.class, o -> {
            o.setId(1L);
            o.setStatus(InvoiceStatusEnum.INVOICINGSUCCESSFUL.getCode());
        });
        when(invoiceRecordsMapper.selectById(1L)).thenReturn(invoiceRecordsDO);

        InvoiceConfirmReqVO reqVO = new InvoiceConfirmReqVO();
        reqVO.setId(1L);

        assertServiceException(() -> invoiceService.confirmInvoice(reqVO, null), THIS_INVOICE_IS_FAIL);
    }

    @Test
    @WithMockLoginUser
    void confirmInvoice_whenRecordAlreadyFailed_thenThrowException() {
        // 准备数据
        InvoiceRecordsDO invoiceRecordsDO = randomPojo(InvoiceRecordsDO.class, o -> {
            o.setId(1L);
            o.setStatus(InvoiceStatusEnum.INVOICINGFAILED.getCode());
        });
        when(invoiceRecordsMapper.selectById(1L)).thenReturn(invoiceRecordsDO);

        InvoiceConfirmReqVO reqVO = new InvoiceConfirmReqVO();
        reqVO.setId(1L);

        assertServiceException(() -> invoiceService.confirmInvoice(reqVO, null), THIS_INVOICE_IS_FAIL);
    }

    @Test
    @WithMockLoginUser
    void confirmInvoice_whenPurchaseRecordNotExists_thenThrowException() {
        // 准备数据
        InvoiceRecordsDO invoiceRecordsDO = randomPojo(InvoiceRecordsDO.class, o -> {
            o.setId(1L);
            o.setStatus(InvoiceStatusEnum.PENDINGINVOICING.getCode());
            o.setPurchaseIds("[1]");
            o.setInvoiceTitleId(1L);
            o.setOrganId(1L);
        });
        when(invoiceRecordsMapper.selectById(1L)).thenReturn(invoiceRecordsDO);

        InvoiceConfirmReqVO reqVO = new InvoiceConfirmReqVO();
        reqVO.setId(1L);

        assertServiceException(() -> invoiceService.confirmInvoice(reqVO, null), PURCHASE_RECORD_NO_EXIST);
    }

    @Test
    @WithMockLoginUser
    void confirmInvoice_agreeWithoutFile_thenThrowException() {
        // 准备数据
        InvoiceRecordsDO invoiceRecordsDO = randomPojo(InvoiceRecordsDO.class, o -> {
            o.setId(1L);
            o.setStatus(InvoiceStatusEnum.PENDINGINVOICING.getCode());
            o.setPurchaseIds("[1]");
            o.setInvoiceTitleId(1L);
            o.setOrganId(1L);
        });
        when(invoiceRecordsMapper.selectById(1L)).thenReturn(invoiceRecordsDO);

        InvoiceTitleInfoDO title = randomPojo(InvoiceTitleInfoDO.class, o -> {
            o.setId(1L);
            o.setOrganId(1L);
            o.setInvoiceTitle("测试公司");
        });
        when(invoiceTitleInfoMapper.selectById(anyLong())).thenReturn(title);

        PurchaseRecordDO purchase = randomPojo(PurchaseRecordDO.class, o -> {
            o.setId(1L);
            o.setOrganId(1L);
            o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
            o.setIsInvocing(InvoiceStatusEnum.INVOICABLE.getCode());
            o.setInitial(false);
        });
        when(purchaseRecordMapper.selectByIds(anyList())).thenReturn(List.of(purchase));

        InvoiceConfirmReqVO reqVO = new InvoiceConfirmReqVO();
        reqVO.setId(1L);
        reqVO.setAgree(true);

        assertServiceException(() -> invoiceService.confirmInvoice(reqVO, null), ORG_INVOICE_AGREE_FILE_NULL_ERROR);
    }

    @Test
    @WithMockLoginUser
    void confirmInvoice_rejectWithoutRemark_thenThrowException() {
        // 准备数据
        InvoiceRecordsDO invoiceRecordsDO = randomPojo(InvoiceRecordsDO.class, o -> {
            o.setId(1L);
            o.setStatus(InvoiceStatusEnum.PENDINGINVOICING.getCode());
            o.setPurchaseIds("[1]");
            o.setInvoiceTitleId(1L);
            o.setOrganId(1L);
        });
        when(invoiceRecordsMapper.selectById(1L)).thenReturn(invoiceRecordsDO);

        InvoiceTitleInfoDO title = randomPojo(InvoiceTitleInfoDO.class, o -> {
            o.setId(1L);
            o.setOrganId(1L);
            o.setInvoiceTitle("测试公司");
        });
        when(invoiceTitleInfoMapper.selectById(anyLong())).thenReturn(title);

        PurchaseRecordDO purchase = randomPojo(PurchaseRecordDO.class, o -> {
            o.setId(1L);
            o.setOrganId(1L);
            o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
            o.setIsInvocing(InvoiceStatusEnum.INVOICABLE.getCode());
            o.setInitial(false);
        });
        when(purchaseRecordMapper.selectByIds(anyList())).thenReturn(List.of(purchase));

        InvoiceConfirmReqVO reqVO = new InvoiceConfirmReqVO();
        reqVO.setId(1L);
        reqVO.setAgree(false);
        reqVO.setRemark(null);

        assertServiceException(() -> invoiceService.confirmInvoice(reqVO, null), INVOICING_REJECT_REASON);
    }

    @Test
    @WithMockLoginUser(username = "测试用户", userId = 1L)
    void confirmInvoice_agreeWithFile_thenSuccess() {
        // 准备数据
        InvoiceRecordsDO invoiceRecordsDO = randomPojo(InvoiceRecordsDO.class, o -> {
            o.setId(1L);
            o.setStatus(InvoiceStatusEnum.PENDINGINVOICING.getCode());
            o.setPurchaseIds("[1]");
            o.setInvoiceTitleId(1L);
            o.setOrganId(1L);
            o.setInvoiceAmount(new BigDecimal("100.00"));
        });
        when(invoiceRecordsMapper.selectById(1L)).thenReturn(invoiceRecordsDO);

        InvoiceTitleInfoDO title = randomPojo(InvoiceTitleInfoDO.class, o -> {
            o.setId(1L);
            o.setOrganId(1L);
            o.setInvoiceTitle("测试公司");
        });
        when(invoiceTitleInfoMapper.selectById(anyLong())).thenReturn(title);

        PurchaseRecordDO purchase = randomPojo(PurchaseRecordDO.class, o -> {
            o.setId(1L);
            o.setOrganId(1L);
            o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
            o.setIsInvocing(InvoiceStatusEnum.INVOICABLE.getCode());
            o.setInitial(false);
        });
        when(purchaseRecordMapper.selectByIds(anyList())).thenReturn(List.of(purchase));

        // Mock 文件上传
        Long fileId = 999L;
        MockMultipartFile file = new MockMultipartFile("file", "测试公司-20231010101010-100.pdf", "application/pdf", "content".getBytes());
        when(fileApi.createFileAndReturnId(any(byte[].class), anyString())).thenReturn(fileId);

        InvoiceConfirmReqVO reqVO = new InvoiceConfirmReqVO();
        reqVO.setId(1L);
        reqVO.setAgree(true);
        reqVO.setInvoiceNo("FP20231010101010");
        reqVO.setRemark("同意开票");

        // 调用方法
        assertDoesNotThrow(() -> invoiceService.confirmInvoice(reqVO, file));

        // 验证数据库更新
        verify(invoiceRecordsMapper).updateById(argThat((InvoiceRecordsDO recordsDO) ->
                recordsDO.getStatus().equals(InvoiceStatusEnum.INVOICINGSUCCESSFUL.getCode())
                        && recordsDO.getRemark().equals(reqVO.getRemark())
                        && recordsDO.getInvoiceAttachmentId().equals(fileId)
                        && recordsDO.getInvoiceNo().equals(reqVO.getInvoiceNo())
        ));

        verify(purchaseRecordMapper, times(1))
                .updateBatch(anyList());
    }

    @Test
    @WithMockLoginUser(userId = 1L, username = "测试用户")
    void confirmInvoice_reject_thenSuccess() {
        // 准备数据
        InvoiceRecordsDO invoiceRecordsDO = randomPojo(InvoiceRecordsDO.class, o -> {
            o.setId(1L);
            o.setStatus(InvoiceStatusEnum.PENDINGINVOICING.getCode());
            o.setPurchaseIds("[1]");
            o.setInvoiceTitleId(1L);
            o.setOrganId(1L);
        });
        when(invoiceRecordsMapper.selectById(1L)).thenReturn(invoiceRecordsDO);

        InvoiceTitleInfoDO title = randomPojo(InvoiceTitleInfoDO.class, o -> {
            o.setId(1L);
            o.setOrganId(1L);
            o.setInvoiceTitle("测试公司");
        });
        when(invoiceTitleInfoMapper.selectById(anyLong())).thenReturn(title);

        PurchaseRecordDO purchase = randomPojo(PurchaseRecordDO.class, o -> {
            o.setId(1L);
            o.setOrganId(1L);
            o.setPaymentMethod(PayChannelCodeEnum.ALIPAY_PC.getCode());
            o.setIsInvocing(InvoiceStatusEnum.INVOICABLE.getCode());
            o.setInitial(false);
        });
        when(purchaseRecordMapper.selectByIds(anyList())).thenReturn(List.of(purchase));

        InvoiceConfirmReqVO reqVO = new InvoiceConfirmReqVO();
        reqVO.setId(1L);
        reqVO.setAgree(false);
        reqVO.setRemark("拒绝开票");

        // 调用方法
        assertDoesNotThrow(() -> invoiceService.confirmInvoice(reqVO, null));

        // 验证数据库更新
        verify(invoiceRecordsMapper).updateById(argThat((InvoiceRecordsDO recordsDO) ->
                recordsDO.getStatus().equals(InvoiceStatusEnum.INVOICINGFAILED.getCode())
                        && recordsDO.getRemark().equals(reqVO.getRemark())
        ));

        verify(purchaseRecordMapper, times(1))
                .updateBatch(anyList());
    }

    @Test
    @WithMockLoginUser
    void getInvoiceAmount_whenNoData_thenReturnZeroValues() {
        InvoiceAmountRespVO invoiceAmountRespVO = new InvoiceAmountRespVO();
        invoiceAmountRespVO.setInvocable(BigDecimal.ZERO);
        invoiceAmountRespVO.setInTheInvoice(BigDecimal.ONE);
        invoiceAmountRespVO.setInvoiced(BigDecimal.TEN);
        when(purchaseRecordMapper.getOrgInvoiceAmountStatistics(anyLong())).thenReturn(invoiceAmountRespVO);

        // 调用方法
        InvoiceAmountRespVO result = invoiceService.getInvoiceAmount();
        
        // 验证结果
        assertNotNull(result);
        assertEquals(BigDecimal.ZERO, result.getInvocable());
        assertEquals(BigDecimal.ONE, result.getInTheInvoice());
        assertEquals(BigDecimal.TEN, result.getInvoiced());
    }
    @Test
    void getManageInvoiceAmount_whenNoData_thenReturnZeroValues() {
        InvoiceAmountRespVO invoiceAmountRespVO = new InvoiceAmountRespVO();
        invoiceAmountRespVO.setPendingInvoice(BigDecimal.TEN);
        invoiceAmountRespVO.setInvoiced(BigDecimal.ZERO);
        when(invoiceRecordsMapper.getManageInvoiceAmountStatistics()).thenReturn(invoiceAmountRespVO);

        // 调用方法
        InvoiceAmountRespVO result = invoiceService.getManageInvoiceAmount();
        
        // 验证结果
        assertNotNull(result);
        assertEquals(BigDecimal.TEN, result.getPendingInvoice());
        assertEquals(BigDecimal.ZERO, result.getInvoiced());
    }

    @Test
    void getFileExtension_whenFileNameIsNull_thenReturnEmpty() {
        assertEquals("", InvoiceServiceImpl.getFileExtension(null));
    }

    @Test
    void getFileExtension_whenFileNameIsEmpty_thenReturnEmpty() {
        assertEquals("", InvoiceServiceImpl.getFileExtension(""));
    }

    @Test
    void getFileExtension_whenFileNameHasNoExtension_thenReturnEmpty() {
        assertEquals("", InvoiceServiceImpl.getFileExtension("filename"));
    }

    @Test
    void getFileExtension_whenFileNameEndsWithDot_thenReturnEmpty() {
        assertEquals("", InvoiceServiceImpl.getFileExtension("filename."));
    }

    @Test
    void getFileExtension_whenFileNameHasExtension_thenReturnExtension() {
        assertEquals(".pdf", InvoiceServiceImpl.getFileExtension("filename.pdf"));
    }

    @Test
    void getFileExtension_whenFileNameHasMultipleDots_thenReturnLastExtension() {
        assertEquals(".pdf", InvoiceServiceImpl.getFileExtension("file.name.pdf"));
    }

    @Test
    void getFileExtension_whenFileNameHasExtensionInUpperCase_thenReturnLowerCaseExtension() {
        assertEquals(".pdf", InvoiceServiceImpl.getFileExtension("filename.PDF"));
    }

}