package com.ruoyi.info.collection.service;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation;
import com.ruoyi.info.collection.domain.CcdiStaffFmyRelation;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationEditDTO;
import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationOptionVO;
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
import com.ruoyi.info.collection.service.impl.CcdiStaffEnterpriseRelationServiceImpl;
import com.ruoyi.info.collection.service.support.EnterpriseAutoFillService;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.session.Configuration;
import org.junit.jupiter.api.AfterEach;
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.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;

import java.util.List;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class CcdiStaffEnterpriseRelationServiceImplTest {

    @BeforeAll
    static void initTableInfo() {
        registerTableInfo(CcdiStaffEnterpriseRelation.class, CcdiStaffEnterpriseRelationMapper.class.getName());
        registerTableInfo(CcdiStaffFmyRelation.class, CcdiStaffFmyRelationMapper.class.getName());
    }

    @InjectMocks
    private CcdiStaffEnterpriseRelationServiceImpl service;

    @Mock
    private CcdiStaffEnterpriseRelationMapper relationMapper;

    @Mock
    private CcdiStaffFmyRelationMapper familyRelationMapper;

    @Mock
    private ICcdiStaffEnterpriseRelationImportService relationImportService;

    @Mock
    private RedisTemplate<String, Object> redisTemplate;

    @Mock
    private EnterpriseAutoFillService enterpriseAutoFillService;

    @AfterEach
    void clearSecurityContext() {
        SecurityContextHolder.clearContext();
    }

    @Test
    void insertRelation_shouldAllowValidFamily() {
        mockLoginUser("tester");
        CcdiStaffEnterpriseRelationAddDTO addDTO = buildAddDto();
        CcdiStaffFmyRelation familyRelation = new CcdiStaffFmyRelation();
        familyRelation.setRelationCertNo(addDTO.getPersonId());
        familyRelation.setRelationName("李四");
        familyRelation.setPersonId("320101199001010011");

        when(familyRelationMapper.selectOne(any())).thenReturn(familyRelation);
        when(relationMapper.existsByPersonIdAndSocialCreditCode(addDTO.getPersonId(), addDTO.getSocialCreditCode())).thenReturn(false);
        when(relationMapper.insert(any(CcdiStaffEnterpriseRelation.class))).thenReturn(1);

        int result = service.insertRelation(addDTO);

        assertEquals(1, result);
        ArgumentCaptor<CcdiStaffEnterpriseRelation> captor = ArgumentCaptor.forClass(CcdiStaffEnterpriseRelation.class);
        verify(relationMapper).insert(captor.capture());
        assertEquals(1, captor.getValue().getStatus());
        assertEquals("MANUAL", captor.getValue().getDataSource());
        assertEquals(1, captor.getValue().getIsEmpFamily());
        verify(enterpriseAutoFillService).ensureExists(argThat(item ->
                "91310000123456789A".equals(item.socialCreditCode())
                        && "测试企业".equals(item.enterpriseName())
                        && "EMP_RELATION".equals(item.entSource())
                        && "MANUAL".equals(item.dataSource())
                        && "tester".equals(item.userName())
        ));
    }

    @Test
    void insertRelation_shouldRejectInvalidFamily() {
        CcdiStaffEnterpriseRelationAddDTO addDTO = buildAddDto();

        when(familyRelationMapper.selectOne(any()))
                .thenReturn(null)
                .thenReturn(new CcdiStaffFmyRelation());

        RuntimeException exception = assertThrows(RuntimeException.class, () -> service.insertRelation(addDTO));

        assertEquals("亲属身份证号[" + addDTO.getPersonId() + "]不是有效员工亲属，请先维护有效的员工亲属关系", exception.getMessage());
    }

    @Test
    void insertRelation_shouldRejectMissingFamily() {
        CcdiStaffEnterpriseRelationAddDTO addDTO = buildAddDto();

        when(familyRelationMapper.selectOne(any())).thenReturn(null);

        RuntimeException exception = assertThrows(RuntimeException.class, () -> service.insertRelation(addDTO));

        assertEquals("亲属身份证号[" + addDTO.getPersonId() + "]不存在，请先维护员工亲属关系", exception.getMessage());
    }

    @Test
    void updateRelation_shouldExecuteEditFlow() {
        CcdiStaffEnterpriseRelationEditDTO editDTO = new CcdiStaffEnterpriseRelationEditDTO();
        editDTO.setId(1L);
        editDTO.setPersonId("320101199001010022");
        editDTO.setSocialCreditCode("91310000123456789A");
        editDTO.setEnterpriseName("测试企业");
        editDTO.setRelationPersonPost("董事");
        editDTO.setStatus(0);
        editDTO.setRemark("测试备注");

        when(relationMapper.update(isNull(), any(LambdaUpdateWrapper.class))).thenReturn(1);

        int result = service.updateRelation(editDTO);

        assertEquals(1, result);
        verify(relationMapper).update(isNull(), any(LambdaUpdateWrapper.class));
    }

    @Test
    void selectFamilyOptions_shouldDelegateToMapper() {
        CcdiStaffEnterpriseRelationOptionVO option = new CcdiStaffEnterpriseRelationOptionVO();
        option.setRelationCertNo("320101199001010022");
        option.setRelationName("李四");
        option.setStaffPersonId("320101199001010011");
        option.setStaffPersonName("张三");
        List<CcdiStaffEnterpriseRelationOptionVO> expected = List.of(option);

        when(relationMapper.selectFamilyOptions("320101")).thenReturn(expected);

        List<CcdiStaffEnterpriseRelationOptionVO> result = service.selectFamilyOptions("320101");

        assertSame(expected, result);
    }

    private CcdiStaffEnterpriseRelationAddDTO buildAddDto() {
        CcdiStaffEnterpriseRelationAddDTO addDTO = new CcdiStaffEnterpriseRelationAddDTO();
        addDTO.setPersonId("320101199001010022");
        addDTO.setSocialCreditCode("91310000123456789A");
        addDTO.setEnterpriseName("测试企业");
        addDTO.setRelationPersonPost("董事");
        return addDTO;
    }

    private static void registerTableInfo(Class<?> entityClass, String namespace) {
        if (TableInfoHelper.getTableInfo(entityClass) != null) {
            return;
        }
        MapperBuilderAssistant assistant = new MapperBuilderAssistant(new Configuration(), "");
        assistant.setCurrentNamespace(namespace);
        TableInfoHelper.initTableInfo(assistant, entityClass);
    }

    private void mockLoginUser(String userName) {
        SysUser user = new SysUser();
        user.setUserName(userName);
        LoginUser loginUser = new LoginUser(1L, 1L, user, Set.of());
        UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(loginUser, null, List.of());
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }
}
