package cn.novalon.manage.sys.audit;

import cn.novalon.manage.sys.core.domain.OperationLog;
import cn.novalon.manage.sys.core.service.IOperationLogService;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.web.reactive.function.server.ServerRequest;
import reactor.core.publisher.Mono;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;

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

/**
 * OperationLogAspect 单元测试
 * 
 * @author 张翔
 * @date 2026-04-03
 */
@ExtendWith(MockitoExtension.class)
class OperationLogAspectTest {

    @Mock
    private IOperationLogService logService;

    @Mock
    private ProceedingJoinPoint joinPoint;

    @Mock
    private Signature signature;

    @Mock
    private ServerRequest serverRequest;

    @Mock
    private ServerRequest.Headers headers;

    private OperationLogAspect aspect;
    private ObjectMapper objectMapper;

    @BeforeEach
    void setUp() {
        objectMapper = new ObjectMapper();
        aspect = new OperationLogAspect(logService, objectMapper);
        
        // 默认mock行为
        lenient().when(serverRequest.headers()).thenReturn(headers);
        lenient().when(headers.firstHeader(any())).thenReturn(null);
    }

    @Test
    @DisplayName("当方法返回Mono成功时,应保存操作日志")
    void around_whenMonoSuccess_shouldSaveLog() throws Throwable {
        cn.novalon.manage.sys.audit.OperationLog annotation = createTestAnnotation("创建用户", "用户管理");
        
        when(joinPoint.getSignature()).thenReturn(signature);
        when(signature.toShortString()).thenReturn("SysUserHandler.createUser");
        when(joinPoint.getArgs()).thenReturn(new Object[]{serverRequest});
        when(joinPoint.proceed()).thenReturn(Mono.just("success"));
        when(logService.save(any(OperationLog.class))).thenReturn(Mono.just(new OperationLog()));

        Object result = aspect.around(joinPoint, annotation);

        StepVerifier.create((Mono<?>) result)
                .expectNextMatches(obj -> "success".equals(obj))
                .verifyComplete();

        verify(logService, timeout(1000)).save(any(OperationLog.class));
    }

    @Test
    @DisplayName("当方法返回Mono失败时,应保存错误日志")
    void around_whenMonoError_shouldSaveErrorLog() throws Throwable {
        cn.novalon.manage.sys.audit.OperationLog annotation = createTestAnnotation("删除用户", "用户管理");
        RuntimeException testError = new RuntimeException("删除失败");
        
        when(joinPoint.getSignature()).thenReturn(signature);
        when(signature.toShortString()).thenReturn("SysUserHandler.deleteUser");
        when(joinPoint.getArgs()).thenReturn(new Object[]{serverRequest});
        when(joinPoint.proceed()).thenReturn(Mono.error(testError));
        when(logService.save(any(OperationLog.class))).thenReturn(Mono.just(new OperationLog()));

        Object result = aspect.around(joinPoint, annotation);

        StepVerifier.create((Mono<?>) result)
                .expectError(RuntimeException.class)
                .verify();

        verify(logService, timeout(1000)).save(argThat(log -> 
                "1".equals(log.getStatus()) && "删除失败".equals(log.getErrorMsg())
        ));
    }

    @Test
    @DisplayName("当方法返回Flux成功时,应保存操作日志")
    void around_whenFluxSuccess_shouldSaveLog() throws Throwable {
        cn.novalon.manage.sys.audit.OperationLog annotation = createTestAnnotation("查询用户列表", "用户管理");
        
        when(joinPoint.getSignature()).thenReturn(signature);
        when(signature.toShortString()).thenReturn("SysUserHandler.listUsers");
        when(joinPoint.getArgs()).thenReturn(new Object[]{serverRequest});
        when(joinPoint.proceed()).thenReturn(Flux.just("user1", "user2", "user3"));
        when(logService.save(any(OperationLog.class))).thenReturn(Mono.just(new OperationLog()));

        Object result = aspect.around(joinPoint, annotation);

        StepVerifier.create((Flux<?>) result)
                .expectNextMatches(obj -> "user1".equals(obj))
                .expectNextMatches(obj -> "user2".equals(obj))
                .expectNextMatches(obj -> "user3".equals(obj))
                .verifyComplete();

        verify(logService, timeout(1000)).save(any(OperationLog.class));
    }

    @Test
    @DisplayName("当方法抛出异常时,应保存错误日志并重新抛出")
    void around_whenMethodThrowsException_shouldSaveLogAndRethrow() throws Throwable {
        cn.novalon.manage.sys.audit.OperationLog annotation = createTestAnnotation("更新用户", "用户管理");
        RuntimeException testError = new RuntimeException("更新失败");
        
        when(joinPoint.getSignature()).thenReturn(signature);
        when(signature.toShortString()).thenReturn("SysUserHandler.updateUser");
        when(joinPoint.getArgs()).thenReturn(new Object[]{serverRequest});
        when(joinPoint.proceed()).thenThrow(testError);
        when(logService.save(any(OperationLog.class))).thenReturn(Mono.just(new OperationLog()));

        assertThrows(RuntimeException.class, () -> {
            aspect.around(joinPoint, annotation);
        });

        verify(logService, timeout(1000)).save(argThat(log -> 
                "1".equals(log.getStatus()) && "更新失败".equals(log.getErrorMsg())
        ));
    }

    @Test
    @DisplayName("当参数过大时,应截断参数")
    void around_whenParamsTooLarge_shouldTruncate() throws Throwable {
        cn.novalon.manage.sys.audit.OperationLog annotation = createTestAnnotation("创建用户", "用户管理");
        
        StringBuilder largeParam = new StringBuilder();
        for (int i = 0; i < 3000; i++) {
            largeParam.append("a");
        }
        
        when(joinPoint.getSignature()).thenReturn(signature);
        when(signature.toShortString()).thenReturn("SysUserHandler.createUser");
        when(joinPoint.getArgs()).thenReturn(new Object[]{largeParam.toString()});
        when(joinPoint.proceed()).thenReturn(Mono.just("success"));
        when(logService.save(any(OperationLog.class))).thenReturn(Mono.just(new OperationLog()));

        Object result = aspect.around(joinPoint, annotation);

        StepVerifier.create((Mono<?>) result)
                .expectNextMatches(obj -> "success".equals(obj))
                .verifyComplete();

        verify(logService, timeout(1000)).save(argThat(log -> {
            String params = log.getParams();
            return params != null && params.contains("truncated");
        }));
    }

    @Test
    @DisplayName("当没有ServerRequest参数时,IP应为unknown")
    void around_whenNoServerRequest_shouldUseUnknownIp() throws Throwable {
        cn.novalon.manage.sys.audit.OperationLog annotation = createTestAnnotation("创建用户", "用户管理");
        
        when(joinPoint.getSignature()).thenReturn(signature);
        when(signature.toShortString()).thenReturn("SysUserHandler.createUser");
        when(joinPoint.getArgs()).thenReturn(new Object[]{"param1", "param2"});
        when(joinPoint.proceed()).thenReturn(Mono.just("success"));
        when(logService.save(any(OperationLog.class))).thenReturn(Mono.just(new OperationLog()));

        Object result = aspect.around(joinPoint, annotation);

        StepVerifier.create((Mono<?>) result)
                .expectNextMatches(obj -> "success".equals(obj))
                .verifyComplete();

        verify(logService, timeout(1000)).save(argThat(log -> 
                "unknown".equals(log.getIp())
        ));
    }

    @Test
    @DisplayName("当日志保存失败时,不应影响主流程")
    void around_whenLogSaveFails_shouldNotAffectMainFlow() throws Throwable {
        cn.novalon.manage.sys.audit.OperationLog annotation = createTestAnnotation("创建用户", "用户管理");
        
        when(joinPoint.getSignature()).thenReturn(signature);
        when(signature.toShortString()).thenReturn("SysUserHandler.createUser");
        when(joinPoint.getArgs()).thenReturn(new Object[]{serverRequest});
        when(joinPoint.proceed()).thenReturn(Mono.just("success"));
        when(logService.save(any(OperationLog.class))).thenReturn(Mono.error(new RuntimeException("数据库错误")));

        Object result = aspect.around(joinPoint, annotation);

        StepVerifier.create((Mono<?>) result)
                .expectNextMatches(obj -> "success".equals(obj))
                .verifyComplete();
    }

    @Test
    @DisplayName("当方法返回非响应式类型时,应直接返回")
    void around_whenNonReactiveResult_shouldReturnDirectly() throws Throwable {
        cn.novalon.manage.sys.audit.OperationLog annotation = createTestAnnotation("同步操作", "测试模块");
        
        when(joinPoint.getSignature()).thenReturn(signature);
        when(signature.toShortString()).thenReturn("TestHandler.syncOperation");
        when(joinPoint.getArgs()).thenReturn(new Object[]{});
        when(joinPoint.proceed()).thenReturn("sync-result");

        Object result = aspect.around(joinPoint, annotation);

        assertEquals("sync-result", result);
        verify(logService, never()).save(any());
    }

    private cn.novalon.manage.sys.audit.OperationLog createTestAnnotation(String operation, String module) {
        return new cn.novalon.manage.sys.audit.OperationLog() {
            @Override
            public String operation() {
                return operation;
            }

            @Override
            public String module() {
                return module;
            }

            @Override
            public Class<? extends java.lang.annotation.Annotation> annotationType() {
                return cn.novalon.manage.sys.audit.OperationLog.class;
            }
        };
    }
}
