package com.yimeng.okr.service;

import com.yimeng.okr.entity.KpiConfig;
import com.yimeng.okr.entity.OkrObjective;
import com.yimeng.okr.entity.AssessmentPeriod;
import com.yimeng.okr.entity.PeriodParticipant;
import com.yimeng.okr.entity.SysUser;
import com.yimeng.okr.enums.PeriodStatus;
import com.yimeng.okr.enums.ScoreType;
import com.yimeng.okr.enums.UserRole;
import com.yimeng.okr.exception.BusinessException;
import com.yimeng.okr.mapper.*;
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.springframework.context.ApplicationEventPublisher;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class AssessmentPeriodServiceTest {

    @Mock AssessmentPeriodMapper periodMapper;
    @Mock SysUserMapper userMapper;
    @Mock OkrObjectiveMapper objectiveMapper;
    @Mock KrKeyResultMapper krMapper;
    @Mock KpiConfigMapper kpiConfigMapper;
    @Mock PerformanceScoreMapper scoreMapper;
    @Mock PerformanceFeedbackMapper feedbackMapper;
    @Mock PeriodParticipantMapper participantMapper;
    @Mock ApplicationEventPublisher publisher;
    @InjectMocks AssessmentPeriodService periodService;

    @Test
    void transitionToExecutingRejectsPendingOkrReview() {
        AssessmentPeriod period = period(1L, PeriodStatus.OKR_ALIGN);
        when(periodMapper.selectById(1L)).thenReturn(period);
        when(objectiveMapper.selectCount(any())).thenReturn(1L);

        BusinessException ex = assertThrows(BusinessException.class,
                () -> periodService.transitionStatus(1L, PeriodStatus.EXECUTING));

        assertTrue(ex.getMessage().contains("待审核OKR"));
        verify(periodMapper, never()).updateById(any());
    }

    @Test
    void transitionToArchivedRejectsUnconfirmedFinalScores() {
        AssessmentPeriod period = period(2L, PeriodStatus.ASSESSING);
        when(periodMapper.selectById(2L)).thenReturn(period);
        when(scoreMapper.selectCount(any())).thenReturn(1L, 1L);

        BusinessException ex = assertThrows(BusinessException.class,
                () -> periodService.transitionStatus(2L, PeriodStatus.ARCHIVED));

        assertTrue(ex.getMessage().contains("未确认最终评分"));
        verify(periodMapper, never()).updateById(any());
    }

    @Test
    void transitionToExecutingRejectsParticipantsWithoutApprovedOkr() {
        AssessmentPeriod period = period(3L, PeriodStatus.OKR_ALIGN);
        when(periodMapper.selectById(3L)).thenReturn(period);
        when(objectiveMapper.selectCount(any())).thenReturn(0L);
        when(kpiConfigMapper.selectCount(any())).thenReturn(0L);
        OkrObjective approved = new OkrObjective();
        approved.setUserId(10L);
        approved.setStatus("APPROVED");
        when(objectiveMapper.selectList(any())).thenReturn(List.of(approved));
        KpiConfig locked10 = lockedKpi(10L);
        KpiConfig locked11 = lockedKpi(11L);
        when(kpiConfigMapper.selectList(any())).thenReturn(List.of(locked10, locked11));

        BusinessException ex = assertThrows(BusinessException.class,
                () -> periodService.transitionStatus(3L, PeriodStatus.EXECUTING));

        assertTrue(ex.getMessage().contains("未提交或未通过OKR"));
        verify(periodMapper, never()).updateById(any());
    }

    @Test
    void transitionToArchivedRejectsMissingSelfScoreAndFeedbackReply() {
        AssessmentPeriod period = period(4L, PeriodStatus.ASSESSING);
        when(periodMapper.selectById(4L)).thenReturn(period);
        when(scoreMapper.selectCount(any())).thenReturn(1L, 0L);
        when(kpiConfigMapper.selectList(any())).thenReturn(List.of(lockedKpi(10L)));

        BusinessException ex = assertThrows(BusinessException.class,
                () -> periodService.transitionStatus(4L, PeriodStatus.ARCHIVED));

        assertTrue(ex.getMessage().contains("未完成自评"));
        verify(periodMapper, never()).updateById(any());
    }

    @Test
    void readinessUsesOnlyUsersWithPeriodDataAsParticipants() {
        AssessmentPeriod period = period(5L, PeriodStatus.OKR_ALIGN);
        when(periodMapper.selectById(5L)).thenReturn(period);
        when(objectiveMapper.selectCount(any())).thenReturn(0L);
        when(kpiConfigMapper.selectCount(any())).thenReturn(0L);
        OkrObjective approved = new OkrObjective();
        approved.setUserId(10L);
        approved.setStatus("APPROVED");
        when(objectiveMapper.selectList(any())).thenReturn(List.of(approved));
        when(kpiConfigMapper.selectList(any())).thenReturn(List.of(lockedKpi(10L)));

        var readiness = periodService.checkReadiness(5L, PeriodStatus.EXECUTING);

        assertTrue(readiness.isReady());
        assertTrue(readiness.getBlockers().isEmpty());
        verify(userMapper, never()).selectList(any());
    }

    @Test
    void transitionToOkrAlignCreatesActiveParticipantSnapshotForActiveDepartmentUsers() {
        AssessmentPeriod period = period(6L, PeriodStatus.DRAFT);
        when(periodMapper.selectById(6L)).thenReturn(period);
        SysUser active = participant(10L);
        active.setStatus("ACTIVE");
        SysUser disabled = participant(11L);
        disabled.setStatus("DISABLED");
        when(userMapper.selectList(any())).thenReturn(List.of(active, disabled));

        periodService.transitionStatus(6L, PeriodStatus.OKR_ALIGN);

        verify(participantMapper).insert(argThat(p ->
                p instanceof PeriodParticipant
                        && ((PeriodParticipant) p).getPeriodId().equals(6L)
                        && ((PeriodParticipant) p).getUserId().equals(10L)
                        && "ACTIVE".equals(((PeriodParticipant) p).getStatus())));
        verify(participantMapper, never()).insert(argThat(p ->
                p instanceof PeriodParticipant && ((PeriodParticipant) p).getUserId().equals(11L)));
    }

    @Test
    void readinessIgnoresExemptParticipant() {
        AssessmentPeriod period = period(7L, PeriodStatus.OKR_ALIGN);
        when(periodMapper.selectById(7L)).thenReturn(period);
        when(objectiveMapper.selectCount(any())).thenReturn(0L);
        when(kpiConfigMapper.selectCount(any())).thenReturn(0L);
        PeriodParticipant active = participantRecord(7L, 10L, "ACTIVE");
        PeriodParticipant exempt = participantRecord(7L, 11L, "EXEMPT");
        when(participantMapper.selectList(any())).thenReturn(List.of(active, exempt));
        OkrObjective approved = new OkrObjective();
        approved.setUserId(10L);
        approved.setStatus("APPROVED");
        when(objectiveMapper.selectList(any())).thenReturn(List.of(approved));
        when(kpiConfigMapper.selectList(any())).thenReturn(List.of(lockedKpi(10L)));

        var readiness = periodService.checkReadiness(7L, PeriodStatus.EXECUTING);

        assertTrue(readiness.isReady());
    }

    @Test
    void addParticipantCreatesActiveParticipantWhenUserExists() {
        when(userMapper.selectById(12L)).thenReturn(participant(12L));
        when(participantMapper.selectOne(any())).thenReturn(null);

        PeriodParticipant participant = periodService.addParticipant(7L, 12L);

        assertEquals("ACTIVE", participant.getStatus());
        assertEquals(7L, participant.getPeriodId());
        assertEquals(12L, participant.getUserId());
        verify(participantMapper).insert(participant);
    }

    @Test
    void activateParticipantRevertsExemption() {
        PeriodParticipant participant = participantRecord(7L, 12L, "EXEMPT");
        participant.setReason("休假");
        when(participantMapper.selectOne(any())).thenReturn(participant);

        PeriodParticipant active = periodService.activateParticipant(7L, 12L);

        assertEquals("ACTIVE", active.getStatus());
        assertNull(active.getReason());
        assertNull(active.getExemptedAt());
        verify(participantMapper).updateById(participant);
    }

    private AssessmentPeriod period(Long id, PeriodStatus status) {
        AssessmentPeriod period = new AssessmentPeriod();
        period.setId(id);
        period.setName("2026年6月");
        period.setStatus(status);
        return period;
    }

    private SysUser participant(Long id) {
        SysUser user = new SysUser();
        user.setId(id);
        user.setUsername("u" + id);
        user.setRealName("员工" + id);
        user.setDepartmentId(1L);
        user.setRole(UserRole.EMPLOYEE);
        return user;
    }

    private PeriodParticipant participantRecord(Long periodId, Long userId, String status) {
        PeriodParticipant participant = new PeriodParticipant();
        participant.setPeriodId(periodId);
        participant.setUserId(userId);
        participant.setStatus(status);
        return participant;
    }

    private KpiConfig lockedKpi(Long userId) {
        KpiConfig config = new KpiConfig();
        config.setUserId(userId);
        config.setLockedAt(java.time.LocalDateTime.now());
        return config;
    }
}
