package com.ruoyi.web.modules.pay.service;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.math.BigDecimal;
import java.util.Date;
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.mockito.junit.jupiter.MockitoExtension;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.web.modules.order.domain.BizOrderAftersale;
import com.ruoyi.web.modules.pay.config.WeChatPayProperties;
import com.ruoyi.web.modules.pay.constant.PayConstants;
import com.ruoyi.web.modules.pay.domain.BizPayRecord;
import com.ruoyi.web.modules.pay.domain.BizRefundRecord;
import com.ruoyi.web.modules.pay.mapper.BizPayRecordMapper;
import com.ruoyi.web.modules.pay.mapper.BizRefundRecordMapper;
import com.ruoyi.web.modules.pay.service.impl.OrderAftersaleRefundServiceImpl;
import com.ruoyi.web.modules.pay.support.WeChatPaySupport;

@ExtendWith(MockitoExtension.class)
class OrderAftersaleRefundServiceImplTest
{
    private static final Long AFTERSALE_ID = 900L;

    private static final Long ORDER_ID = 500L;

    @Mock
    private BizRefundRecordMapper refundRecordMapper;

    @Mock
    private BizPayRecordMapper payRecordMapper;

    @Mock
    private WeChatPayProperties payProperties;

    @Mock
    private WeChatPaySupport paySupport;

    @Mock
    private IShopWechatPayService shopWechatPayService;

    @InjectMocks
    private OrderAftersaleRefundServiceImpl refundService;

    @Test
    void refundOnAftersaleFinish_mockMode_success()
    {
        BizOrderAftersale aftersale = buildAftersale();
        BizPayRecord payRecord = buildPayRecord();
        when(refundRecordMapper.selectByAftersaleId(AFTERSALE_ID)).thenReturn(null);
        when(payRecordMapper.selectLatestByOrderId(ORDER_ID)).thenReturn(payRecord);
        when(refundRecordMapper.sumSuccessRefundAmountByOrderId(ORDER_ID)).thenReturn(BigDecimal.ZERO);
        when(payProperties.isRealPayEnabled()).thenReturn(false);
        when(refundRecordMapper.insert(any(BizRefundRecord.class))).thenAnswer(inv -> {
            BizRefundRecord r = inv.getArgument(0);
            r.setRefundId(1L);
            return 1;
        });
        when(refundRecordMapper.updateForSuccess(eq(1L), eq("MOCK_RFAS20250526000001"),
                eq(PayConstants.REFUND_STATUS_SUCCESS), any(Date.class))).thenReturn(1);

        refundService.refundOnAftersaleFinish(aftersale);

        ArgumentCaptor<BizRefundRecord> captor = ArgumentCaptor.forClass(BizRefundRecord.class);
        verify(refundRecordMapper).insert(captor.capture());
        assertEquals("RFAS20250526000001", captor.getValue().getOutRefundNo());
        assertEquals(new BigDecimal("88.00"), captor.getValue().getRefundAmount());
        verify(paySupport, never()).createDomesticRefund(any(), any(), any(), any(), any(), any());
    }

    @Test
    void refundOnAftersaleFinish_alreadyRefunded_skips()
    {
        BizRefundRecord existing = new BizRefundRecord();
        existing.setRefundStatus(PayConstants.REFUND_STATUS_SUCCESS);
        when(refundRecordMapper.selectByAftersaleId(AFTERSALE_ID)).thenReturn(existing);

        refundService.refundOnAftersaleFinish(buildAftersale());

        verify(payRecordMapper, never()).selectLatestByOrderId(any());
        verify(refundRecordMapper, never()).insert(any());
    }

    @Test
    void refundOnAftersaleFinish_noPayRecord_throws()
    {
        when(refundRecordMapper.selectByAftersaleId(AFTERSALE_ID)).thenReturn(null);
        when(payRecordMapper.selectLatestByOrderId(ORDER_ID)).thenReturn(null);

        ServiceException ex = assertThrows(ServiceException.class,
                () -> refundService.refundOnAftersaleFinish(buildAftersale()));
        assertEquals(PayConstants.MSG_REFUND_PAY_NOT_FOUND, ex.getMessage());
    }

    @Test
    void refundOnAftersaleFinish_exceedBalance_throws()
    {
        BizOrderAftersale aftersale = buildAftersale();
        BizPayRecord payRecord = buildPayRecord();
        when(refundRecordMapper.selectByAftersaleId(AFTERSALE_ID)).thenReturn(null);
        when(payRecordMapper.selectLatestByOrderId(ORDER_ID)).thenReturn(payRecord);
        when(refundRecordMapper.sumSuccessRefundAmountByOrderId(ORDER_ID)).thenReturn(new BigDecimal("50.00"));

        ServiceException ex = assertThrows(ServiceException.class,
                () -> refundService.refundOnAftersaleFinish(aftersale));
        assertEquals(PayConstants.MSG_REFUND_EXCEED, ex.getMessage());
    }

    private BizOrderAftersale buildAftersale()
    {
        BizOrderAftersale aftersale = new BizOrderAftersale();
        aftersale.setAftersaleId(AFTERSALE_ID);
        aftersale.setAftersaleNo("AS20250526000001");
        aftersale.setOrderId(ORDER_ID);
        aftersale.setOrderNo("O20250526000001");
        aftersale.setMemberId(10L);
        aftersale.setShopId(101L);
        aftersale.setApplyAmount(new BigDecimal("88.00"));
        return aftersale;
    }

    private BizPayRecord buildPayRecord()
    {
        BizPayRecord payRecord = new BizPayRecord();
        payRecord.setPayId(200L);
        payRecord.setOrderId(ORDER_ID);
        payRecord.setPayAmount(new BigDecimal("100.00"));
        payRecord.setPayStatus(PayConstants.PAY_STATUS_SUCCESS);
        payRecord.setWxTransactionId("4200001234567890");
        return payRecord;
    }
}
