package com.yimeng.okr.service;

import com.yimeng.okr.entity.PerformanceScore;
import com.yimeng.okr.entity.ScoreAppeal;
import com.yimeng.okr.entity.SysUser;
import com.yimeng.okr.enums.ScoreType;
import com.yimeng.okr.enums.UserRole;
import com.yimeng.okr.event.NotificationEvent;
import com.yimeng.okr.exception.BusinessException;
import com.yimeng.okr.mapper.PerformanceScoreMapper;
import com.yimeng.okr.mapper.ScoreAppealMapper;
import com.yimeng.okr.mapper.SysUserMapper;
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 ScoreAppealServiceTest {

    @Mock ScoreAppealMapper appealMapper;
    @Mock PerformanceScoreMapper scoreMapper;
    @Mock SysUserMapper userMapper;
    @Mock AuthorizationService authorizationService;
    @Mock ApplicationEventPublisher publisher;
    @InjectMocks ScoreAppealService appealService;

    @Test
    void submitAppealRequiresPublishedFinalScore() {
        PerformanceScore score = finalScore("PENDING_PUBLISH");
        when(scoreMapper.selectOne(any())).thenReturn(score);

        BusinessException ex = assertThrows(BusinessException.class,
                () -> appealService.submitAppeal(1L, 2L, "分数有异议"));

        assertTrue(ex.getMessage().contains("发布"));
        verify(appealMapper, never()).insert(any());
    }

    @Test
    void submitAppealCreatesOpenAppeal() {
        PerformanceScore score = finalScore("PUBLISHED");
        score.setId(9L);
        when(scoreMapper.selectOne(any())).thenReturn(score);
        when(userMapper.selectList(any())).thenReturn(List.of(user(90L, UserRole.HR_ADMIN), user(91L, UserRole.SUPER_ADMIN)));

        ScoreAppeal appeal = appealService.submitAppeal(1L, 2L, "分数有异议");

        assertEquals("OPEN", appeal.getStatus());
        assertEquals(9L, appeal.getScoreId());
        assertNotNull(appeal.getSubmittedAt());
        verify(appealMapper).insert(appeal);
        verify(publisher, times(2)).publishEvent(any(NotificationEvent.class));
    }

    @Test
    void resolveAppealRequiresHrAndClosesAppeal() {
        ScoreAppeal appeal = new ScoreAppeal();
        appeal.setId(5L);
        appeal.setUserId(2L);
        appeal.setStatus("OPEN");
        when(appealMapper.selectById(5L)).thenReturn(appeal);
        when(authorizationService.isHrOrAbove(99L)).thenReturn(true);

        ScoreAppeal resolved = appealService.resolveAppeal(5L, 99L, "ACCEPTED", "已调整");

        assertEquals("ACCEPTED", resolved.getStatus());
        assertEquals("已调整", resolved.getReply());
        assertEquals(99L, resolved.getResolvedBy());
        assertNotNull(resolved.getResolvedAt());
        verify(appealMapper).updateById(appeal);
        verify(publisher).publishEvent(any(NotificationEvent.class));
    }

    @Test
    void listAppealsRequiresHr() {
        when(authorizationService.isHrOrAbove(8L)).thenReturn(false);

        BusinessException ex = assertThrows(BusinessException.class,
                () -> appealService.listAppeals(8L, "OPEN"));

        assertEquals(403, ex.getCode());
        verify(appealMapper, never()).selectList(any());
    }

    private PerformanceScore finalScore(String status) {
        PerformanceScore score = new PerformanceScore();
        score.setPeriodId(1L);
        score.setUserId(2L);
        score.setType(ScoreType.FINAL);
        score.setStatus(status);
        return score;
    }

    private SysUser user(Long id, UserRole role) {
        SysUser user = new SysUser();
        user.setId(id);
        user.setRole(role);
        return user;
    }
}
