package com.ruoyi.app.flashdelivery.controller;

import com.ruoyi.app.flashdelivery.dto.FlashDeliveryCreateRequest;
import com.ruoyi.app.flashdelivery.dto.FlashDeliveryOrderDetailView;
import com.ruoyi.app.flashdelivery.dto.FlashDeliveryProofRequest;
import com.ruoyi.app.flashdelivery.dto.FlashDeliveryDeliverRequest;
import com.ruoyi.app.flashdelivery.dto.FlashDeliveryQuoteView;
import com.ruoyi.app.flashdelivery.exception.FlashDeliveryQuoteChangedException;
import com.ruoyi.app.flashdelivery.service.FlashDeliveryApplicationService;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.spring.SpringUtils;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.GetMapping;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.system.utils.Auth;
import com.ruoyi.system.utils.JwtUtil;

import java.lang.reflect.Method;
import java.math.BigDecimal;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

class FlashDeliveryControllerContractTest {
    private static ConfigurableListableBeanFactory originalBeanFactory;

    @BeforeAll
    static void initializeMessages() {
        originalBeanFactory = (ConfigurableListableBeanFactory)
                ReflectionTestUtils.getField(SpringUtils.class, "beanFactory");
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        StaticMessageSource messageSource = new StaticMessageSource();
        messageSource.setUseCodeAsDefaultMessage(true);
        beanFactory.registerSingleton("messageSource", messageSource);
        new SpringUtils().postProcessBeanFactory(beanFactory);
    }

    @AfterAll
    static void restoreMessages() {
        new SpringUtils().postProcessBeanFactory(originalBeanFactory);
    }

    @Test
    void appControllersReadIdentityFromTokenAndUseRequestBodies() throws Exception {
        Method create = FlashDeliveryUserController.class.getMethod("create", String.class,
                FlashDeliveryCreateRequest.class);
        assertNotNull(create.getParameters()[0].getAnnotation(RequestHeader.class));
        assertNotNull(create.getParameters()[1].getAnnotation(RequestBody.class));

        Method pickup = FlashDeliveryRiderController.class.getMethod("pickup", String.class,
                Long.class, FlashDeliveryProofRequest.class);
        assertNotNull(pickup.getParameters()[0].getAnnotation(RequestHeader.class));
        assertNotNull(pickup.getParameters()[2].getAnnotation(RequestBody.class));

        Method deliver = FlashDeliveryRiderController.class.getMethod("deliver", String.class,
                Long.class, FlashDeliveryDeliverRequest.class);
        assertNotNull(deliver.getParameters()[2].getAnnotation(RequestBody.class));
    }

    @Test
    void appOrderListsUseSimplePageAndRiderFoodOrderParameters() throws Exception {
        Method userOrders = FlashDeliveryUserController.class.getMethod("orders",
                String.class, int.class, int.class, String.class);
        assertNotNull(userOrders.getParameters()[0].getAnnotation(RequestHeader.class));
        assertNotNull(userOrders.getParameters()[1].getAnnotation(RequestParam.class));
        assertNotNull(userOrders.getParameters()[2].getAnnotation(RequestParam.class));
        assertNotNull(userOrders.getParameters()[3].getAnnotation(RequestParam.class));
        assertTrue(!userOrders.getParameters()[3].getAnnotation(RequestParam.class).required());

        Method riderOrders = FlashDeliveryRiderController.class.getMethod("orders",
                String.class, int.class, int.class, String.class, BigDecimal.class, BigDecimal.class);
        assertArrayEquals(new String[]{"/orders"}, riderOrders.getAnnotation(GetMapping.class).value());
        for (int index = 1; index < riderOrders.getParameterCount(); index++) {
            assertNotNull(riderOrders.getParameters()[index].getAnnotation(RequestParam.class));
        }

        assertThrows(NoSuchMethodException.class,
                () -> FlashDeliveryRiderController.class.getMethod("available",
                        String.class, int.class, int.class, String.class));
        assertThrows(NoSuchMethodException.class,
                () -> FlashDeliveryRiderController.class.getMethod("mine",
                        String.class, int.class, int.class, String.class, String.class));
    }

    @Test
    void appDetailAndAcceptPutDirectOrderDtoInAjaxData() {
        FlashDeliveryApplicationService service = mock(FlashDeliveryApplicationService.class);
        FlashDeliveryOrderDetailView detail = new FlashDeliveryOrderDetailView();
        detail.setId(10L);
        when(service.userDetail(7L, 10L)).thenReturn(detail);
        when(service.riderDetail(8L, 10L)).thenReturn(detail);
        when(service.accept(8L, 10L)).thenReturn(detail);

        AjaxResult userResult = new FlashDeliveryUserController(service)
                .detail(JwtUtil.token("7", "user"), 10L);
        AjaxResult riderResult = new FlashDeliveryRiderController(service)
                .detail(JwtUtil.token("8", "rider"), 10L);
        AjaxResult acceptResult = new FlashDeliveryRiderController(service)
                .accept(JwtUtil.token("8", "rider"), 10L);

        assertSame(detail, userResult.get(AjaxResult.DATA_TAG));
        assertSame(detail, riderResult.get(AjaxResult.DATA_TAG));
        assertSame(detail, acceptResult.get(AjaxResult.DATA_TAG));
    }

    @Test
    void createReturnsLatestQuoteWhenClientQuoteChanged() {
        FlashDeliveryApplicationService service = mock(FlashDeliveryApplicationService.class);
        FlashDeliveryCreateRequest request = new FlashDeliveryCreateRequest();
        FlashDeliveryQuoteView latest = new FlashDeliveryQuoteView();
        latest.setAmount(120L);
        when(service.create(7L, request)).thenThrow(new FlashDeliveryQuoteChangedException(latest));

        AjaxResult result = new FlashDeliveryUserController(service)
                .create(JwtUtil.token("7", "user"), request);

        assertSame(latest, result.get(AjaxResult.DATA_TAG));
        assertTrue(((Number) result.get(AjaxResult.CODE_TAG)).intValue() != 200);
    }

    @Test
    void everyAdminEndpointDeclaresPlatformPermission() {
        for (Method method : FlashDeliveryAdminController.class.getDeclaredMethods()) {
            if (method.isSynthetic()) continue;
            assertNotNull(method.getAnnotation(PreAuthorize.class), method.getName());
            assertTrue(method.getAnnotation(PreAuthorize.class).value().contains("flash:"));
        }
    }

    @Test
    void pricingAndOrderInterventionsAreOperationAudited() throws Exception {
        assertNotNull(FlashDeliveryAdminController.class.getMethod("createPricing",
                com.ruoyi.app.flashdelivery.dto.FlashDeliveryPricingRequest.class).getAnnotation(Log.class));
        assertNotNull(FlashDeliveryAdminController.class.getMethod("updatePricing", Long.class,
                com.ruoyi.app.flashdelivery.dto.FlashDeliveryPricingRequest.class).getAnnotation(Log.class));
        assertNotNull(FlashDeliveryAdminController.class.getMethod("deletePricing", Long.class).getAnnotation(Log.class));
        assertNotNull(FlashDeliveryAdminController.class.getMethod("cancel", Long.class,
                com.ruoyi.app.flashdelivery.dto.FlashDeliveryReasonRequest.class).getAnnotation(Log.class));
        assertNotNull(FlashDeliveryAdminController.class.getMethod("complete", Long.class).getAnnotation(Log.class));
    }

    @Test
    void everyAppEndpointDeclaresMethodLevelTokenAuthentication() {
        for (Class<?> controller : new Class<?>[]{FlashDeliveryUserController.class,
                FlashDeliveryRiderController.class}) {
            for (Method method : controller.getDeclaredMethods()) {
                if (!java.lang.reflect.Modifier.isPublic(method.getModifiers())) continue;
                assertNotNull(method.getAnnotation(Auth.class), controller.getSimpleName() + "." + method.getName());
            }
        }
    }
}
