package com.ruoyi.ccdi.project.service.impl;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import com.ruoyi.ccdi.project.domain.entity.CcdiBankTagTask;
import com.ruoyi.ccdi.project.domain.enums.TriggerType;
import com.ruoyi.ccdi.project.mapper.CcdiBankTagTaskMapper;
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
import com.ruoyi.common.exception.ServiceException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.slf4j.LoggerFactory;

import java.util.concurrent.Executor;

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 ProjectBankTagRebuildCoordinatorTest {

    @InjectMocks
    private ProjectBankTagRebuildCoordinator coordinator;

    @Mock
    private CcdiBankTagTaskMapper taskMapper;

    @Mock
    private CcdiBankTagServiceImpl bankTagService;

    @Mock
    private ICcdiProjectService projectService;

    @Mock
    private Executor tagRebuildExecutor;

    @Test
    void submitManualRebuild_shouldRejectWhenProjectAlreadyRunning() {
        CcdiBankTagTask runningTask = new CcdiBankTagTask();
        runningTask.setId(1L);
        runningTask.setProjectId(40L);
        runningTask.setStatus("RUNNING");
        when(taskMapper.selectRunningTaskByProjectId(40L)).thenReturn(runningTask);

        assertThrows(ServiceException.class, () -> coordinator.submitManual(40L, null, "admin"));
        verify(bankTagService, never()).rebuildProject(40L, null, "admin", TriggerType.MANUAL);
    }

    @Test
    void submitAutoRebuild_shouldMarkNeedRerunWhenProjectAlreadyRunning() {
        CcdiBankTagTask runningTask = new CcdiBankTagTask();
        runningTask.setId(1L);
        runningTask.setProjectId(40L);
        runningTask.setStatus("RUNNING");
        runningTask.setNeedRerun(0);
        when(taskMapper.selectRunningTaskByProjectId(40L)).thenReturn(runningTask);

        coordinator.submitAuto(40L, TriggerType.AUTO_BATCH_UPLOAD);

        verify(taskMapper).updateTask(any(CcdiBankTagTask.class));
        verify(bankTagService, never()).rebuildProject(40L, null, "system", TriggerType.AUTO_BATCH_UPLOAD);
    }

    @Test
    void submitManual_shouldLogRejectWhenProjectAlreadyRunning() {
        CcdiBankTagTask runningTask = new CcdiBankTagTask();
        runningTask.setId(1L);
        runningTask.setProjectId(40L);
        runningTask.setStatus("RUNNING");
        when(taskMapper.selectRunningTaskByProjectId(40L)).thenReturn(runningTask);

        Logger logger = (Logger) LoggerFactory.getLogger(ProjectBankTagRebuildCoordinator.class);
        ListAppender<ILoggingEvent> logAppender = new ListAppender<>();
        logAppender.start();
        logger.addAppender(logAppender);

        try {
            assertThrows(ServiceException.class, () -> coordinator.submitManual(40L, null, "admin"));
            assertTrue(logAppender.list.stream().map(ILoggingEvent::getFormattedMessage)
                .anyMatch(message -> message.contains("拒绝手动重算")
                    && message.contains("projectId=40")
                    && message.contains("operator=admin")));
        } finally {
            logger.detachAppender(logAppender);
        }
    }

    @Test
    void submitAuto_shouldLogNeedRerunWhenProjectAlreadyRunning() {
        CcdiBankTagTask runningTask = new CcdiBankTagTask();
        runningTask.setId(1L);
        runningTask.setProjectId(40L);
        runningTask.setStatus("RUNNING");
        runningTask.setNeedRerun(0);
        when(taskMapper.selectRunningTaskByProjectId(40L)).thenReturn(runningTask);

        Logger logger = (Logger) LoggerFactory.getLogger(ProjectBankTagRebuildCoordinator.class);
        ListAppender<ILoggingEvent> logAppender = new ListAppender<>();
        logAppender.start();
        logger.addAppender(logAppender);

        try {
            coordinator.submitAuto(40L, TriggerType.AUTO_BATCH_UPLOAD);
            assertTrue(logAppender.list.stream().map(ILoggingEvent::getFormattedMessage)
                .anyMatch(message -> message.contains("已标记完成后补跑")
                    && message.contains("projectId=40")
                    && message.contains("runningTaskId=1")
                    && message.contains("AUTO_BATCH_UPLOAD")));
        } finally {
            logger.detachAppender(logAppender);
        }
    }

    @Test
    void shouldRejectSubmittingRebuildForArchivedProject() {
        org.mockito.Mockito.doThrow(new ServiceException("已归档项目不允许重新进入打标流程"))
            .when(projectService).ensureProjectCanStartTagging(40L);

        assertThrows(ServiceException.class,
            () -> coordinator.submitManual(40L, null, "tester"));
    }

    @Test
    void submitAuto_shouldDispatchRebuildAsynchronously() {
        coordinator.submitAuto(40L, TriggerType.AUTO_PARAM_CHANGE);

        verify(tagRebuildExecutor).execute(any(Runnable.class));
        verify(bankTagService, never()).rebuildProject(40L, null, "system", TriggerType.AUTO_PARAM_CHANGE);
    }
}
