package com.ruoyi.info.collection.service;

import com.ruoyi.info.collection.domain.CcdiAssetInfo;
import com.ruoyi.info.collection.domain.excel.CcdiAssetInfoExcel;
import com.ruoyi.info.collection.domain.vo.AssetImportFailureVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.mapper.CcdiAssetInfoMapper;
import com.ruoyi.info.collection.service.impl.CcdiAssetInfoImportServiceImpl;
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.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class CcdiAssetInfoImportServiceImplTest {

    @InjectMocks
    private CcdiAssetInfoImportServiceImpl service;

    @Mock
    private CcdiAssetInfoMapper assetInfoMapper;

    @Mock
    private RedisTemplate<String, Object> redisTemplate;

    @Mock
    private ICcdiAssetInfoImportService assetInfoImportService;

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

    @Mock
    private ValueOperations<String, Object> valueOperations;

    @Test
    void assetInfoExcel_shouldExcludeAssetIdAndFamilyId() {
        Set<String> fieldNames = Arrays.stream(CcdiAssetInfoExcel.class.getDeclaredFields())
                .map(field -> field.getName())
                .collect(Collectors.toSet());

        assertFalse(fieldNames.contains("assetId"));
        assertFalse(fieldNames.contains("familyId"));
        assertTrue(fieldNames.contains("personId"));
    }

    @Test
    void importAssetInfo_shouldUseDedicatedAssetTaskKeys() {
        List<CcdiAssetInfoExcel> excelList = List.of(buildExcel("320101199001010011", "房产"));
        when(redisTemplate.opsForHash()).thenReturn(hashOperations);

        String taskId = service.importAssetInfo(excelList);

        verify(hashOperations).putAll(eq("import:assetInfo:" + taskId), anyMap());
        verify(redisTemplate).expire("import:assetInfo:" + taskId, 7, TimeUnit.DAYS);
        verify(assetInfoImportService).importAssetInfoAsync(eq(excelList), eq(taskId), any());
    }

    @Test
    void importAssetInfoAsync_shouldResolveFamilyIdFromFamilyRelationForRelativeAsset() {
        CcdiAssetInfoExcel excel = buildExcel("320101199001010011", "房产");
        when(redisTemplate.opsForHash()).thenReturn(hashOperations);
        when(assetInfoMapper.selectOwnerCandidatesByRelationCertNos(List.of("320101199001010011")))
                .thenReturn(List.of(owner("320101199001010011", "320101199009090099")));

        service.importAssetInfoAsync(List.of(excel), "task-1", "tester");

        ArgumentCaptor<List<CcdiAssetInfo>> captor = ArgumentCaptor.forClass(List.class);
        verify(assetInfoMapper).insertBatch(captor.capture());
        assertEquals("320101199009090099", captor.getValue().get(0).getFamilyId());
        assertEquals("320101199001010011", captor.getValue().get(0).getPersonId());
    }

    @Test
    void importAssetInfoSync_shouldResolveFamilyIdFromCurrentWorkbookRelation() {
        CcdiAssetInfoExcel excel = buildExcel("320101199001010033", "股权");
        when(redisTemplate.opsForHash()).thenReturn(hashOperations);
        when(assetInfoMapper.selectOwnerCandidatesByRelationCertNos(List.of("320101199001010033")))
                .thenReturn(List.of());

        service.importAssetInfoSync(
                List.of(excel),
                "task-current-workbook",
                "tester",
                Map.of("320101199001010033", Set.of("320101199009090099"))
        );

        ArgumentCaptor<List<CcdiAssetInfo>> captor = ArgumentCaptor.forClass(List.class);
        verify(assetInfoMapper).insertBatch(captor.capture());
        assertEquals("320101199009090099", captor.getValue().get(0).getFamilyId());
        assertEquals("320101199001010033", captor.getValue().get(0).getPersonId());
    }

    @Test
    void importAssetInfoAsync_shouldFailWhenEmployeeIdCardIsUsedForFamilyAssetImport() {
        CcdiAssetInfoExcel excel = buildExcel("320101199001010011", "房产");
        when(redisTemplate.opsForHash()).thenReturn(hashOperations);
        when(redisTemplate.opsForValue()).thenReturn(valueOperations);
        when(assetInfoMapper.selectOwnerCandidatesByRelationCertNos(List.of("320101199001010011")))
                .thenReturn(List.of());

        service.importAssetInfoAsync(List.of(excel), "task-self", "tester");

        verify(assetInfoMapper, never()).insertBatch(any());
        ArgumentCaptor<Object> failureCaptor = ArgumentCaptor.forClass(Object.class);
        verify(valueOperations).set(eq("import:assetInfo:task-self:failures"), failureCaptor.capture(), eq(7L), eq(TimeUnit.DAYS));
        AssetImportFailureVO failure = (AssetImportFailureVO) ((List<?>) failureCaptor.getValue()).get(0);
        assertEquals("320101199001010011", failure.getPersonId());
        assertTrue(failure.getErrorMessage().contains("未找到亲属资产归属员工"));
    }

    @Test
    void importAssetInfoAsync_shouldResolveFamilyIdFromFamilyRelationIdCard() {
        CcdiAssetInfoExcel excel = buildExcel("320101199201010022", "车辆");
        when(redisTemplate.opsForHash()).thenReturn(hashOperations);
        when(assetInfoMapper.selectOwnerCandidatesByRelationCertNos(List.of("320101199201010022")))
                .thenReturn(List.of(owner("320101199201010022", "320101199001010011")));

        service.importAssetInfoAsync(List.of(excel), "task-2", "tester");

        ArgumentCaptor<List<CcdiAssetInfo>> captor = ArgumentCaptor.forClass(List.class);
        verify(assetInfoMapper).insertBatch(captor.capture());
        assertEquals("320101199001010011", captor.getValue().get(0).getFamilyId());
        assertEquals("320101199201010022", captor.getValue().get(0).getPersonId());
    }

    @Test
    void importAssetInfoAsync_shouldStoreFailureRowsOnlyForBadRecords() {
        CcdiAssetInfoExcel good = buildExcel("320101199001010011", "房产");
        CcdiAssetInfoExcel bad = buildExcel("320101199001010099", "车辆");

        when(redisTemplate.opsForHash()).thenReturn(hashOperations);
        when(redisTemplate.opsForValue()).thenReturn(valueOperations);
        when(assetInfoMapper.selectOwnerCandidatesByRelationCertNos(List.of("320101199001010011", "320101199001010099")))
                .thenReturn(List.of(owner("320101199001010011", "320101199009090099")));

        service.importAssetInfoAsync(List.of(good, bad), "task-3", "tester");

        ArgumentCaptor<List<CcdiAssetInfo>> insertCaptor = ArgumentCaptor.forClass(List.class);
        verify(assetInfoMapper).insertBatch(insertCaptor.capture());
        assertEquals(1, insertCaptor.getValue().size());
        assertEquals("320101199009090099", insertCaptor.getValue().get(0).getFamilyId());

        ArgumentCaptor<Object> failureCaptor = ArgumentCaptor.forClass(Object.class);
        verify(valueOperations).set(eq("import:assetInfo:task-3:failures"), failureCaptor.capture(), eq(7L), eq(TimeUnit.DAYS));
        List<?> failures = (List<?>) failureCaptor.getValue();
        assertEquals(1, failures.size());
        AssetImportFailureVO failure = (AssetImportFailureVO) failures.get(0);
        assertEquals("320101199001010099", failure.getPersonId());
        assertTrue(failure.getErrorMessage().contains("未找到亲属资产归属员工"));
    }

    @Test
    void importAssetInfoAsync_shouldFailWhenOwnerIsAmbiguous() {
        CcdiAssetInfoExcel excel = buildExcel("320101199201010022", "车辆");
        when(redisTemplate.opsForHash()).thenReturn(hashOperations);
        when(redisTemplate.opsForValue()).thenReturn(valueOperations);
        when(assetInfoMapper.selectOwnerCandidatesByRelationCertNos(List.of("320101199201010022")))
                .thenReturn(List.of(
                        owner("320101199201010022", "320101199001010011"),
                        owner("320101199201010022", "320101199001010033")
                ));

        service.importAssetInfoAsync(List.of(excel), "task-4", "tester");

        verify(assetInfoMapper, never()).insertBatch(any());
        ArgumentCaptor<Object> failureCaptor = ArgumentCaptor.forClass(Object.class);
        verify(valueOperations).set(eq("import:assetInfo:task-4:failures"), failureCaptor.capture(), eq(7L), eq(TimeUnit.DAYS));
        AssetImportFailureVO failure = (AssetImportFailureVO) ((List<?>) failureCaptor.getValue()).get(0);
        assertTrue(failure.getErrorMessage().contains("亲属资产归属员工不唯一"));
    }

    @Test
    void getImportStatusAndFailures_shouldUseAssetPrefixes() {
        when(redisTemplate.opsForHash()).thenReturn(hashOperations);
        when(redisTemplate.opsForValue()).thenReturn(valueOperations);
        when(redisTemplate.hasKey("import:assetInfo:task-5")).thenReturn(true);
        when(hashOperations.entries("import:assetInfo:task-5")).thenReturn(Map.of(
                "taskId", "task-5",
                "status", "SUCCESS",
                "totalCount", 1,
                "successCount", 1,
                "failureCount", 0,
                "progress", 100,
                "startTime", 1L,
                "endTime", 2L,
                "message", "全部成功"
        ));
        AssetImportFailureVO failureVO = new AssetImportFailureVO();
        failureVO.setPersonId("320101199001010099");
        failureVO.setErrorMessage("未找到亲属资产归属员工");
        when(valueOperations.get("import:assetInfo:task-5:failures")).thenReturn(List.of(failureVO));

        ImportStatusVO statusVO = service.getImportStatus("task-5");
        List<AssetImportFailureVO> failures = service.getImportFailures("task-5");

        assertEquals("task-5", statusVO.getTaskId());
        assertEquals("SUCCESS", statusVO.getStatus());
        assertNotNull(failures);
        assertEquals(1, failures.size());
        assertEquals("320101199001010099", failures.get(0).getPersonId());
    }

    private CcdiAssetInfoExcel buildExcel(String personId, String assetMainType) {
        CcdiAssetInfoExcel excel = new CcdiAssetInfoExcel();
        excel.setPersonId(personId);
        excel.setAssetMainType(assetMainType);
        excel.setAssetSubType(assetMainType + "小类");
        excel.setAssetName(assetMainType + "名称");
        excel.setCurrentValue(new BigDecimal("100.00"));
        excel.setAssetStatus("正常");
        return excel;
    }

    private Map<String, String> owner(String personId, String familyId) {
        return Map.of("personId", personId, "familyId", familyId);
    }
}
