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

import com.njcn.rdms.framework.common.exception.ServiceException;
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
import com.njcn.rdms.framework.test.core.ut.BaseMockitoUnitTest;
import com.njcn.rdms.module.project.constant.OvertimeApplicationConstants;
import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationStatusActionReqVO;
import com.njcn.rdms.module.project.dal.dataobject.overtime.OvertimeApplicationDO;
import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusTransitionDO;
import com.njcn.rdms.module.project.dal.mysql.audit.BizAuditLogMapper;
import com.njcn.rdms.module.project.dal.mysql.overtime.OvertimeApplicationMapper;
import com.njcn.rdms.module.project.dal.mysql.overtime.OvertimeApplicationStatusLogMapper;
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper;
import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusTransitionMapper;
import com.njcn.rdms.module.project.enums.ErrorCodeConstants;
import com.njcn.rdms.module.project.service.status.StatusActionTextResolver;
import com.njcn.rdms.module.system.api.user.AdminUserApi;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;

/**
 * 加班申请状态机错误文案单测（TD-012）。
 * 验证：状态机「动作不允许 / 缺原因」抛错时，message 填的是 {@link StatusActionTextResolver} 翻出的中文名，
 * 不外泄英文动作 / 状态 code（技术 token 只进 log，不进用户可见 message）。
 * resolver 自身的 DB 翻译由 StatusActionTextResolverTest 覆盖，这里只验证 service 填进 message 的是中文名。
 */
class OvertimeApplicationServiceImplTest extends BaseMockitoUnitTest {

    @InjectMocks
    private OvertimeApplicationServiceImpl overtimeApplicationService;

    @Mock
    private OvertimeApplicationMapper overtimeApplicationMapper;
    @Mock
    private OvertimeApplicationStatusLogMapper overtimeApplicationStatusLogMapper;
    @Mock
    private BizAuditLogMapper bizAuditLogMapper;
    @Mock
    private ObjectStatusModelMapper objectStatusModelMapper;
    @Mock
    private ObjectStatusTransitionMapper objectStatusTransitionMapper;
    @Mock
    private StatusActionTextResolver statusActionTextResolver;
    @Mock
    private AdminUserApi adminUserApi;

    private static final String OBJECT_TYPE = OvertimeApplicationConstants.STATUS_OBJECT_TYPE;

    @Test
    void approve_whenTransitionNotAllowed_shouldThrowWithChineseNameNoCodeLeak() {
        Long id = 1001L;
        Long loginUserId = 2001L;
        OvertimeApplicationDO application = new OvertimeApplicationDO();
        application.setId(id);
        application.setApproverId(loginUserId);
        application.setStatusCode(OvertimeApplicationConstants.STATUS_APPROVED);
        when(overtimeApplicationMapper.selectById(id)).thenReturn(application);
        // 当前状态 + 动作在状态机表查不到流转 → 不允许
        when(objectStatusTransitionMapper.selectByObjectTypeAndFromStatusAndAction(
                OBJECT_TYPE, OvertimeApplicationConstants.STATUS_APPROVED, OvertimeApplicationConstants.ACTION_APPROVE))
                .thenReturn(null);
        when(statusActionTextResolver.statusName(OBJECT_TYPE, OvertimeApplicationConstants.STATUS_APPROVED))
                .thenReturn("已通过");
        when(statusActionTextResolver.actionName(OBJECT_TYPE, OvertimeApplicationConstants.ACTION_APPROVE))
                .thenReturn("通过");

        try (MockedStatic<SecurityFrameworkUtils> mocked = mockLoginUser(loginUserId)) {
            ServiceException ex = assertThrows(ServiceException.class,
                    () -> overtimeApplicationService.approve(id, new OvertimeApplicationStatusActionReqVO()));
            assertEquals(ErrorCodeConstants.OVERTIME_APPLICATION_STATUS_ACTION_NOT_ALLOWED.getCode(), ex.getCode());
            // message 用中文名
            assertTrue(ex.getMessage().contains("已通过"), "message 应含状态中文名");
            assertTrue(ex.getMessage().contains("通过"), "message 应含动作中文名");
            // 不外泄英文动作 / 状态 code
            assertFalse(ex.getMessage().contains(OvertimeApplicationConstants.ACTION_APPROVE), "message 不应外泄英文动作 code");
            assertFalse(ex.getMessage().contains(OvertimeApplicationConstants.STATUS_APPROVED), "message 不应外泄英文状态 code");
        }
    }

    @Test
    void approve_whenReasonRequiredButBlank_shouldThrowWithChineseActionName() {
        Long id = 1002L;
        Long loginUserId = 2002L;
        OvertimeApplicationDO application = new OvertimeApplicationDO();
        application.setId(id);
        application.setApproverId(loginUserId);
        application.setStatusCode(OvertimeApplicationConstants.STATUS_PENDING);
        when(overtimeApplicationMapper.selectById(id)).thenReturn(application);
        // 流转存在但要求填原因，而入参未带原因 → REASON_REQUIRED
        ObjectStatusTransitionDO transition = new ObjectStatusTransitionDO();
        transition.setNeedReason(Boolean.TRUE);
        when(objectStatusTransitionMapper.selectByObjectTypeAndFromStatusAndAction(
                OBJECT_TYPE, OvertimeApplicationConstants.STATUS_PENDING, OvertimeApplicationConstants.ACTION_APPROVE))
                .thenReturn(transition);
        when(statusActionTextResolver.actionName(OBJECT_TYPE, OvertimeApplicationConstants.ACTION_APPROVE))
                .thenReturn("通过");

        try (MockedStatic<SecurityFrameworkUtils> mocked = mockLoginUser(loginUserId)) {
            ServiceException ex = assertThrows(ServiceException.class,
                    () -> overtimeApplicationService.approve(id, new OvertimeApplicationStatusActionReqVO()));
            assertEquals(ErrorCodeConstants.OVERTIME_APPLICATION_STATUS_ACTION_REASON_REQUIRED.getCode(), ex.getCode());
            assertTrue(ex.getMessage().contains("通过"), "message 应含动作中文名");
            assertFalse(ex.getMessage().contains(OvertimeApplicationConstants.ACTION_APPROVE), "message 不应外泄英文动作 code");
        }
    }

    private MockedStatic<SecurityFrameworkUtils> mockLoginUser(Long loginUserId) {
        MockedStatic<SecurityFrameworkUtils> mocked = mockStatic(SecurityFrameworkUtils.class);
        mocked.when(SecurityFrameworkUtils::getLoginUserId).thenReturn(loginUserId);
        return mocked;
    }
}
