package com.njcn.rdms.module.project.service.duealert;

import com.njcn.rdms.framework.test.core.ut.BaseMockitoUnitTest;
import com.njcn.rdms.module.project.constant.DueAlertConstants;
import com.njcn.rdms.module.project.dal.dataobject.duealert.DueAlertRecordDO;
import com.njcn.rdms.module.project.dal.mysql.duealert.DueAlertRecordMapper;
import com.njcn.rdms.module.project.enums.DueAlertObjectTypeEnum;
import com.njcn.rdms.module.project.framework.notify.NotifySendEvent;
import com.njcn.rdms.module.project.framework.notify.NotifyTemplateCodeConstants;
import com.njcn.rdms.module.system.enums.notify.NotifyMessageLevelConstants;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.dao.DuplicateKeyException;

import java.time.LocalDate;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
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.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * 告警发送服务单测：去重记录落库 + 两组事件（负责人/协办人模板）+ 唯一索引冲突幂等。
 */
class DueAlertSendServiceImplTest extends BaseMockitoUnitTest {

    @InjectMocks
    private DueAlertSendServiceImpl dueAlertSendService;

    @Mock
    private DueAlertRecordMapper dueAlertRecordMapper;
    @Mock
    private ApplicationEventPublisher applicationEventPublisher;

    private static final LocalDate TODAY = LocalDate.of(2026, 6, 15);

    private DueAlertCandidate candidate(List<Long> owners, List<Long> assignees, LocalDate plannedEndDate) {
        DueAlertCandidate c = new DueAlertCandidate();
        c.setObjectType(DueAlertObjectTypeEnum.TASK);
        c.setObjectId(100L);
        c.setObjectName("测试任务");
        c.setPlannedEndDate(plannedEndDate);
        c.setOwnerUserIds(owners);
        c.setAssigneeUserIds(assignees);
        return c;
    }

    @Test
    void sendAlert_approaching_shouldInsertRecordAndPublishBothGroups() {
        DueAlertCandidate c = candidate(List.of(1L), List.of(2L, 3L), TODAY.plusDays(1));

        dueAlertSendService.sendAlert(c, DueAlertConstants.ALERT_TYPE_APPROACHING, TODAY);

        // 去重记录：快照=计划日，alert_date=今天
        ArgumentCaptor<DueAlertRecordDO> recordCaptor = ArgumentCaptor.forClass(DueAlertRecordDO.class);
        verify(dueAlertRecordMapper).insert(recordCaptor.capture());
        DueAlertRecordDO record = recordCaptor.getValue();
        assertEquals("task", record.getObjectType());
        assertEquals(100L, record.getObjectId());
        assertEquals(DueAlertConstants.ALERT_TYPE_APPROACHING, record.getAlertType());
        assertEquals(TODAY.plusDays(1), record.getPlannedEndDate());
        assertEquals(TODAY, record.getAlertDate());

        // 两组事件：负责人版 + 协办人版模板，剩余天数=1
        ArgumentCaptor<NotifySendEvent> eventCaptor = ArgumentCaptor.forClass(NotifySendEvent.class);
        verify(applicationEventPublisher, times(2)).publishEvent(eventCaptor.capture());
        List<NotifySendEvent> events = eventCaptor.getAllValues();
        assertEquals(NotifyTemplateCodeConstants.DUE_ALERT_APPROACHING_OWNER, events.get(0).getTemplateCode());
        assertTrue(events.get(0).getUserIds().contains(1L));
        assertEquals(NotifyTemplateCodeConstants.DUE_ALERT_APPROACHING_ASSIGNEE, events.get(1).getTemplateCode());
        assertTrue(events.get(1).getUserIds().contains(2L));
        assertEquals("任务", events.get(0).getParams().get("objectTypeName"));
        assertEquals("测试任务", events.get(0).getParams().get("objectName"));
        assertEquals("2026-06-16", events.get(0).getParams().get("plannedEndDate"));
        assertEquals(1L, events.get(0).getParams().get("remainingDays"));
        // 对象标识附加参数（未读不重发的匹配键），objectId 为字符串
        assertEquals("task", events.get(0).getParams().get(DueAlertConstants.PARAM_OBJECT_TYPE));
        assertEquals("100", events.get(0).getParams().get(DueAlertConstants.PARAM_OBJECT_ID));
        // 临期：负责人=提醒、协办人=普通
        assertEquals(NotifyMessageLevelConstants.REMIND, events.get(0).getLevel());
        assertEquals(NotifyMessageLevelConstants.NORMAL, events.get(1).getLevel());
    }

    @Test
    void sendAlert_overdue_shouldUseOverdueTemplatesWithoutRemainingDays() {
        DueAlertCandidate c = candidate(List.of(1L), List.of(), TODAY.minusDays(2));

        dueAlertSendService.sendAlert(c, DueAlertConstants.ALERT_TYPE_OVERDUE, TODAY);

        ArgumentCaptor<NotifySendEvent> eventCaptor = ArgumentCaptor.forClass(NotifySendEvent.class);
        // 协办人组为空 → 只发负责人版一条
        verify(applicationEventPublisher, times(1)).publishEvent(eventCaptor.capture());
        NotifySendEvent event = eventCaptor.getValue();
        assertEquals(NotifyTemplateCodeConstants.DUE_ALERT_OVERDUE_OWNER, event.getTemplateCode());
        assertFalse(event.getParams().containsKey("remainingDays"), "逾期模板无剩余天数参数");
        // 逾期负责人=严重
        assertEquals(NotifyMessageLevelConstants.SEVERE, event.getLevel());
    }

    @Test
    void sendAlert_overdue_assignee_shouldBeWarnLevel() {
        DueAlertCandidate c = candidate(List.of(1L), List.of(2L), TODAY.minusDays(1));

        dueAlertSendService.sendAlert(c, DueAlertConstants.ALERT_TYPE_OVERDUE, TODAY);

        ArgumentCaptor<NotifySendEvent> eventCaptor = ArgumentCaptor.forClass(NotifySendEvent.class);
        verify(applicationEventPublisher, times(2)).publishEvent(eventCaptor.capture());
        List<NotifySendEvent> events = eventCaptor.getAllValues();
        assertEquals(NotifyMessageLevelConstants.SEVERE, events.get(0).getLevel());  // 负责人
        assertEquals(NotifyMessageLevelConstants.WARN, events.get(1).getLevel());    // 协办人
    }

    @Test
    void sendAlert_duplicateKey_shouldSkipPublishSilently() {
        DueAlertCandidate c = candidate(List.of(1L), List.of(2L), TODAY.minusDays(1));
        when(dueAlertRecordMapper.insert(any(DueAlertRecordDO.class)))
                .thenThrow(new DuplicateKeyException("uk_due_alert"));

        dueAlertSendService.sendAlert(c, DueAlertConstants.ALERT_TYPE_OVERDUE, TODAY);

        verify(applicationEventPublisher, never()).publishEvent(any(Object.class));
    }

}
