package com.ruoyi.info.collection.service;

import com.ruoyi.common.annotation.DictDropdown;
import com.ruoyi.info.collection.domain.CcdiBizIntermediary;
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryPersonExcel;
import com.ruoyi.info.collection.domain.vo.IntermediaryPersonImportFailureVO;
import com.ruoyi.info.collection.mapper.CcdiBizIntermediaryMapper;
import com.ruoyi.info.collection.service.impl.CcdiIntermediaryPersonImportServiceImpl;
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 org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import java.lang.reflect.Field;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class CcdiIntermediaryPersonImportServiceImplTest {

    @InjectMocks
    private CcdiIntermediaryPersonImportServiceImpl service;

    @Mock
    private CcdiBizIntermediaryMapper intermediaryMapper;

    @Mock
    private RedisTemplate<String, Object> redisTemplate;

    @Mock
    private HashOperations<String, Object, Object> hashOperations;

    @Mock
    private ValueOperations<String, Object> valueOperations;

    @Test
    void intermediaryPersonExcel_shouldUsePersonSubTypeDropdownAndDropRelationType() throws Exception {
        Field personSubTypeField = CcdiIntermediaryPersonExcel.class.getDeclaredField("personSubType");
        DictDropdown dictDropdown = personSubTypeField.getAnnotation(DictDropdown.class);

        assertNotNull(dictDropdown);
        assertEquals("ccdi_person_sub_type", dictDropdown.dictType());
        assertThrows(NoSuchFieldException.class, () -> CcdiIntermediaryPersonExcel.class.getDeclaredField("relationType"));
        assertThrows(NoSuchFieldException.class, () -> IntermediaryPersonImportFailureVO.class.getDeclaredField("relationType"));
    }

    @Test
    void importPersonAsync_shouldFailWhenOwnerRowContainsOwnerPersonIdReference() {
        CcdiIntermediaryPersonExcel owner = buildOwnerExcel("320101199001010014");
        owner.setRelatedNumId("320101199105050053");
        prepareFailureRedisMocks();

        service.importPersonAsync(List.of(owner), "task-owner", "tester");

        verify(intermediaryMapper, never()).insertBatch(any());
        IntermediaryPersonImportFailureVO failure = firstFailure("import:intermediary:task-owner:failures");
        assertEquals("320101199105050053", failure.getRelatedNumId());
        assertTrue(failure.getErrorMessage().contains("本人"));
    }

    @Test
    void importPersonAsync_shouldFailWhenRelativeRowMissesOwnerPersonId() {
        CcdiIntermediaryPersonExcel relative = buildRelativeExcel("320101199201010027", "配偶", null);
        prepareFailureRedisMocks();

        service.importPersonAsync(List.of(relative), "task-relative", "tester");

        verify(intermediaryMapper, never()).insertBatch(any());
        IntermediaryPersonImportFailureVO failure = firstFailure("import:intermediary:task-relative:failures");
        assertEquals("320101199201010027", failure.getPersonId());
        assertTrue(failure.getErrorMessage().contains("关联中介本人证件号码"));
    }

    @Test
    void importPersonAsync_shouldAllowRelativeReferencingSuccessfulOwnerInSameFile() {
        CcdiIntermediaryPersonExcel owner = buildOwnerExcel("320101199001010014");
        CcdiIntermediaryPersonExcel relative = buildRelativeExcel("320101199201010027", "配偶", "320101199001010014");
        prepareStatusRedisMock();
        when(intermediaryMapper.selectList(any())).thenReturn(List.of());

        service.importPersonAsync(List.of(owner, relative), "task-mixed", "tester");

        ArgumentCaptor<List<CcdiBizIntermediary>> captor = ArgumentCaptor.forClass(List.class);
        verify(intermediaryMapper).insertBatch(captor.capture());
        assertEquals(2, captor.getValue().size());
        assertEquals("本人", captor.getValue().get(0).getPersonSubType());
        assertEquals("320101199001010014", captor.getValue().get(1).getRelatedNumId());
        verify(valueOperations, never()).set(any(), any(), any(Long.class), any(TimeUnit.class));
    }

    @Test
    void importPersonAsync_shouldAllowSameRelativePersonIdUnderDifferentOwners() {
        CcdiIntermediaryPersonExcel owner1 = buildOwnerExcel("320101199001010014");
        CcdiIntermediaryPersonExcel owner2 = buildOwnerExcel("320101199003030035");
        CcdiIntermediaryPersonExcel relative1 = buildRelativeExcel("320101199201010027", "配偶", "320101199001010014");
        CcdiIntermediaryPersonExcel relative2 = buildRelativeExcel("320101199201010027", "配偶", "320101199003030035");
        prepareStatusRedisMock();
        when(intermediaryMapper.selectList(any())).thenReturn(List.of());

        service.importPersonAsync(List.of(owner1, owner2, relative1, relative2), "task-owner-scope", "tester");

        ArgumentCaptor<List<CcdiBizIntermediary>> captor = ArgumentCaptor.forClass(List.class);
        verify(intermediaryMapper).insertBatch(captor.capture());
        assertEquals(4, captor.getValue().size());
        verify(valueOperations, never()).set(any(), any(), any(Long.class), any(TimeUnit.class));
    }

    @Test
    void importPersonAsync_shouldRejectDuplicateRelativeUnderSameOwner() {
        CcdiIntermediaryPersonExcel owner = buildOwnerExcel("320101199001010014");
        CcdiIntermediaryPersonExcel relative1 = buildRelativeExcel("320101199201010027", "配偶", "320101199001010014");
        CcdiIntermediaryPersonExcel relative2 = buildRelativeExcel("320101199201010027", "配偶", "320101199001010014");
        prepareFailureRedisMocks();
        when(intermediaryMapper.selectList(any())).thenReturn(List.of());

        service.importPersonAsync(List.of(owner, relative1, relative2), "task-duplicate", "tester");

        ArgumentCaptor<List<CcdiBizIntermediary>> captor = ArgumentCaptor.forClass(List.class);
        verify(intermediaryMapper).insertBatch(captor.capture());
        assertEquals(2, captor.getValue().size());
        IntermediaryPersonImportFailureVO failure = firstFailure("import:intermediary:task-duplicate:failures");
        assertEquals("320101199201010027", failure.getPersonId());
        assertTrue(failure.getErrorMessage().contains("重复"));
    }

    private void prepareStatusRedisMock() {
        when(redisTemplate.opsForHash()).thenReturn(hashOperations);
    }

    private void prepareFailureRedisMocks() {
        prepareStatusRedisMock();
        when(redisTemplate.opsForValue()).thenReturn(valueOperations);
    }

    private IntermediaryPersonImportFailureVO firstFailure(String key) {
        ArgumentCaptor<Object> failureCaptor = ArgumentCaptor.forClass(Object.class);
        verify(valueOperations).set(org.mockito.ArgumentMatchers.eq(key), failureCaptor.capture(),
            org.mockito.ArgumentMatchers.eq(7L), org.mockito.ArgumentMatchers.eq(TimeUnit.DAYS));
        return (IntermediaryPersonImportFailureVO) ((List<?>) failureCaptor.getValue()).get(0);
    }

    private CcdiIntermediaryPersonExcel buildOwnerExcel(String personId) {
        CcdiIntermediaryPersonExcel excel = new CcdiIntermediaryPersonExcel();
        excel.setName("中介本人" + personId.substring(personId.length() - 2));
        excel.setPersonType("中介");
        excel.setPersonSubType("本人");
        excel.setIdType("身份证");
        excel.setPersonId(personId);
        return excel;
    }

    private CcdiIntermediaryPersonExcel buildRelativeExcel(String personId, String personSubType, String ownerPersonId) {
        CcdiIntermediaryPersonExcel excel = new CcdiIntermediaryPersonExcel();
        excel.setName("中介亲属" + personId.substring(personId.length() - 2));
        excel.setPersonType("中介");
        excel.setPersonSubType(personSubType);
        excel.setIdType("身份证");
        excel.setPersonId(personId);
        excel.setRelatedNumId(ownerPersonId);
        return excel;
    }
}
