package com.yimeng.okr.aspect;

import com.yimeng.okr.annotation.OperationLogAnnotation;
import com.yimeng.okr.entity.AssessmentPeriod;
import com.yimeng.okr.entity.OperationLog;
import com.yimeng.okr.mapper.OperationLogMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;

import java.lang.reflect.Method;
import java.time.LocalDate;
import java.util.Map;

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.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;

class OperationLogAspectTest {

    @Test
    void operationLogDoesNotBlockBusinessWhenParameterContainsLocalDate() throws Throwable {
        OperationLogMapper logMapper = mock(OperationLogMapper.class);
        OperationLogAspect aspect = new OperationLogAspect(logMapper);

        AssessmentPeriod period = new AssessmentPeriod();
        period.setName("2026年6月");
        period.setStartDate(LocalDate.of(2026, 6, 1));
        period.setEndDate(LocalDate.of(2026, 6, 30));

        Method method = SampleController.class.getDeclaredMethod("create", AssessmentPeriod.class);
        MethodSignature signature = mock(MethodSignature.class);
        when(signature.getMethod()).thenReturn(method);

        ProceedingJoinPoint joinPoint = mock(ProceedingJoinPoint.class);
        when(joinPoint.getSignature()).thenReturn(signature);
        when(joinPoint.getArgs()).thenReturn(new Object[]{period});
        when(joinPoint.proceed()).thenReturn("ok");

        Object result = aspect.around(joinPoint);

        assertEquals("ok", result);
        verify(joinPoint).proceed();
        verify(logMapper).insert(any());
    }

    @Test
    void sensitiveOperationMasksParamsAndResult() throws Throwable {
        OperationLogMapper logMapper = mock(OperationLogMapper.class);
        OperationLogAspect aspect = new OperationLogAspect(logMapper);

        Method method = SampleController.class.getDeclaredMethod("changePassword", Map.class);
        MethodSignature signature = mock(MethodSignature.class);
        when(signature.getMethod()).thenReturn(method);

        ProceedingJoinPoint joinPoint = mock(ProceedingJoinPoint.class);
        when(joinPoint.getSignature()).thenReturn(signature);
        when(joinPoint.getArgs()).thenReturn(new Object[]{Map.of(
                "oldPassword", "old-secret",
                "newPassword", "new-secret",
                "commentsJson", "{\"score\":88}")});
        when(joinPoint.proceed()).thenReturn(Map.of("accessToken", "jwt-token", "total", 88));

        aspect.around(joinPoint);

        ArgumentCaptor<OperationLog> captor = ArgumentCaptor.forClass(OperationLog.class);
        verify(logMapper).insert(captor.capture());
        OperationLog log = captor.getValue();
        assertFalse(log.getOldDataJson().contains("old-secret"));
        assertFalse(log.getOldDataJson().contains("new-secret"));
        assertFalse(log.getOldDataJson().contains("{\"score\":88}"));
        assertEquals("\"<sensitive>\"", log.getNewDataJson());
    }

    @Test
    void operationFailureIsAuditedAndRethrown() throws Throwable {
        OperationLogMapper logMapper = mock(OperationLogMapper.class);
        OperationLogAspect aspect = new OperationLogAspect(logMapper);

        Method method = SampleController.class.getDeclaredMethod("changePassword", Map.class);
        MethodSignature signature = mock(MethodSignature.class);
        when(signature.getMethod()).thenReturn(method);

        ProceedingJoinPoint joinPoint = mock(ProceedingJoinPoint.class);
        when(joinPoint.getSignature()).thenReturn(signature);
        when(joinPoint.getArgs()).thenReturn(new Object[]{Map.of("oldPassword", "bad")});
        when(joinPoint.proceed()).thenThrow(new IllegalStateException("原密码错误"));

        assertThrows(IllegalStateException.class, () -> aspect.around(joinPoint));
        verify(logMapper).insert(argThat(log ->
                log instanceof OperationLog
                        && ((OperationLog) log).getNewDataJson().contains("\"success\":false")
                        && !((OperationLog) log).getOldDataJson().contains("bad")));
    }

    static class SampleController {
        @OperationLogAnnotation(action = "CREATE_PERIOD", entityType = "assessment_period")
        String create(AssessmentPeriod period) {
            return "ok";
        }

        @OperationLogAnnotation(action = "CHANGE_PASSWORD", entityType = "sys_user", sensitive = true)
        Map<String, Object> changePassword(Map<String, Object> body) {
            return Map.of("ok", true);
        }
    }
}
