package org.dromara.web.service;

import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.dromara.app.domain.*;
import org.dromara.app.mapper.*;
import org.dromara.app.service.IDeviceCommandService;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.exception.user.CaptchaExpireException;
import org.dromara.common.core.exception.user.UserException;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.SysSocial;
import org.dromara.system.domain.SysUserPost;
import org.dromara.system.domain.SysUserRole;
import org.dromara.system.mapper.SysSocialMapper;
import org.dromara.system.mapper.SysUserMapper;
import org.dromara.system.mapper.SysUserPostMapper;
import org.dromara.system.mapper.SysUserRoleMapper;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.List;

import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
@Tag("dev")
class AccountCancellationServiceUnitTest {

    @Mock
    private SysUserMapper sysUserMapper;
    @Mock
    private SysUserRoleMapper sysUserRoleMapper;
    @Mock
    private SysUserPostMapper sysUserPostMapper;
    @Mock
    private SysSocialMapper sysSocialMapper;
    @Mock
    private AppDeviceMapper appDeviceMapper;
    @Mock
    private AppScheduleMapper appScheduleMapper;
    @Mock
    private AppScheduleDetailMapper appScheduleDetailMapper;
    @Mock
    private AppSchedulingDeviceMapper appSchedulingDeviceMapper;
    @Mock
    private AppWateringLogMapper appWateringLogMapper;
    @Mock
    private AccountCancelCodeService accountCancelCodeService;
    @Mock
    private IDeviceCommandService deviceCommandService;

    @BeforeAll
    static void initTableInfo() {
        MapperBuilderAssistant assistant = new MapperBuilderAssistant(new MybatisConfiguration(), "");
        initTableInfo(assistant, AppDevice.class);
        initTableInfo(assistant, AppSchedule.class);
        initTableInfo(assistant, AppScheduleDetail.class);
        initTableInfo(assistant, AppSchedulingDevice.class);
        initTableInfo(assistant, AppWateringLog.class);
        initTableInfo(assistant, SysSocial.class);
        initTableInfo(assistant, SysUserRole.class);
        initTableInfo(assistant, SysUserPost.class);
    }

    private static void initTableInfo(MapperBuilderAssistant assistant, Class<?> entityClass) {
        TableInfoHelper.remove(entityClass);
        TableInfoHelper.initTableInfo(assistant, entityClass);
    }

    @Test
    void cancelCurrentAccount_deletesCurrentUserAndRelatedData() {
        AccountCancellationService service = newService();
        AppDevice device = new AppDevice();
        device.setDeviceNo("D01");
        AppSchedule schedule = new AppSchedule();
        schedule.setId(10L);
        when(appDeviceMapper.selectList(any(Wrapper.class))).thenReturn(List.of(device));
        when(appScheduleMapper.selectList(any(Wrapper.class))).thenReturn(List.of(schedule));
        when(sysUserMapper.deleteById(100L)).thenReturn(1);

        try (MockedStatic<LoginHelper> loginHelper = mockStatic(LoginHelper.class)) {
            loginHelper.when(LoginHelper::getUserId).thenReturn(100L);
            loginHelper.when(LoginHelper::getUsername).thenReturn("zhangsan");
            loginHelper.when(() -> LoginHelper.isSuperAdmin(100L)).thenReturn(false);

            service.cancelCurrentAccount("123456");

            verify(accountCancelCodeService).validate("zhangsan", "123456");
            verify(deviceCommandService).sendInitDeviceCommand("D01");
            verify(appSchedulingDeviceMapper, atLeastOnce()).delete(any(Wrapper.class));
            verify(appScheduleDetailMapper).delete(any(Wrapper.class));
            verify(appScheduleMapper).delete(any(Wrapper.class));
            verify(appWateringLogMapper, atLeastOnce()).delete(any(Wrapper.class));
            verify(appDeviceMapper).delete(any(Wrapper.class));
            verify(sysSocialMapper).delete(any(Wrapper.class));
            verify(sysUserRoleMapper).delete(any(Wrapper.class));
            verify(sysUserPostMapper).delete(any(Wrapper.class));
            verify(sysUserMapper).deleteById(100L);
            verify(accountCancelCodeService).delete("zhangsan");
        }
    }

    @Test
    void cancelCurrentAccount_rejectsSuperAdmin() {
        AccountCancellationService service = newService();

        try (MockedStatic<LoginHelper> loginHelper = mockStatic(LoginHelper.class)) {
            loginHelper.when(LoginHelper::getUserId).thenReturn(1L);
            loginHelper.when(() -> LoginHelper.isSuperAdmin(1L)).thenReturn(true);

            assertThatThrownBy(() -> service.cancelCurrentAccount("123456"))
                .isInstanceOf(ServiceException.class)
                .hasMessageContaining("超级管理员");

            verifyNoInteractions(
                sysUserMapper,
                sysUserRoleMapper,
                sysUserPostMapper,
                sysSocialMapper,
                appDeviceMapper,
                appScheduleMapper,
                appScheduleDetailMapper,
                appSchedulingDeviceMapper,
                appWateringLogMapper,
                accountCancelCodeService
            );
        }
    }

    @Test
    void cancelCurrentAccount_rejectsInvalidCodeWithoutDeletingData() {
        AccountCancellationService service = newService();

        try (MockedStatic<LoginHelper> loginHelper = mockStatic(LoginHelper.class)) {
            loginHelper.when(LoginHelper::getUserId).thenReturn(100L);
            loginHelper.when(LoginHelper::getUsername).thenReturn("zhangsan");
            loginHelper.when(() -> LoginHelper.isSuperAdmin(100L)).thenReturn(false);
            doThrow(new UserException("验证码无效"))
                .when(accountCancelCodeService).validate("zhangsan", "654321");

            assertThatThrownBy(() -> service.cancelCurrentAccount("654321"))
                .isInstanceOf(UserException.class);

            verifyNoInteractions(
                sysUserMapper,
                sysUserRoleMapper,
                sysUserPostMapper,
                sysSocialMapper,
                appDeviceMapper,
                appScheduleMapper,
                appScheduleDetailMapper,
                appSchedulingDeviceMapper,
                appWateringLogMapper
            );
            verify(accountCancelCodeService).validate("zhangsan", "654321");
            verify(accountCancelCodeService, never()).delete("zhangsan");
        }
    }

    @Test
    void cancelCurrentAccount_rejectsExpiredCodeWithoutDeletingData() {
        AccountCancellationService service = newService();

        try (MockedStatic<LoginHelper> loginHelper = mockStatic(LoginHelper.class)) {
            loginHelper.when(LoginHelper::getUserId).thenReturn(100L);
            loginHelper.when(LoginHelper::getUsername).thenReturn("zhangsan");
            loginHelper.when(() -> LoginHelper.isSuperAdmin(100L)).thenReturn(false);
            doThrow(new CaptchaExpireException())
                .when(accountCancelCodeService).validate("zhangsan", "123456");

            assertThatThrownBy(() -> service.cancelCurrentAccount("123456"))
                .isInstanceOf(CaptchaExpireException.class);

            verifyNoInteractions(
                sysUserMapper,
                sysUserRoleMapper,
                sysUserPostMapper,
                sysSocialMapper,
                appDeviceMapper,
                appScheduleMapper,
                appScheduleDetailMapper,
                appSchedulingDeviceMapper,
                appWateringLogMapper
            );
            verify(accountCancelCodeService).validate("zhangsan", "123456");
            verify(accountCancelCodeService, never()).delete("zhangsan");
        }
    }

    private AccountCancellationService newService() {
        return new AccountCancellationService(
            sysUserMapper,
            sysUserRoleMapper,
            sysUserPostMapper,
            sysSocialMapper,
            appDeviceMapper,
            appScheduleMapper,
            appScheduleDetailMapper,
            appSchedulingDeviceMapper,
            appWateringLogMapper,
            accountCancelCodeService,
            deviceCommandService
        );
    }
}
