package com.ruoyi.app.user;

import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.system.domain.InfoUser;
import com.ruoyi.system.domain.PosOrder;
import com.ruoyi.system.service.IInfoUserService;
import com.ruoyi.system.service.IPosOrderService;
import com.ruoyi.system.service.IUserWalletService;
import com.ruoyi.system.utils.AuthContext;
import com.ruoyi.system.utils.JwtUtil;
import com.ruoyi.common.utils.spring.SpringUtils;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.MockedStatic;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.test.util.ReflectionTestUtils;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Locale;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

class InfoUserControllerTest {

    private InfoUserController controller;
    private IPosOrderService posOrderService;
    private IInfoUserService infoUserService;
    private IUserWalletService userWalletService;
    private MerchantTokenSessionService merchantTokenSessionService;
    private static ConfigurableListableBeanFactory originalBeanFactory;

    @BeforeAll
    static void initializeTableMetadata() {
        TableInfoHelper.initTableInfo(
                new MapperBuilderAssistant(new MybatisConfiguration(), ""), PosOrder.class);
        TableInfoHelper.initTableInfo(
                new MapperBuilderAssistant(new MybatisConfiguration(), ""), InfoUser.class);
        originalBeanFactory = (ConfigurableListableBeanFactory)
                ReflectionTestUtils.getField(SpringUtils.class, "beanFactory");
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        StaticMessageSource messageSource = new StaticMessageSource();
        messageSource.addMessage("no.user.audit.reject.reason.required", Locale.getDefault(),
                "审核不通过时必须填写审核不通过原因");
        messageSource.addMessage("no.action.success", Locale.getDefault(), "操作成功");
        messageSource.addMessage("merchant.subaccount.platform.managed", Locale.getDefault(),
                "分管账号只能通过专用入口管理");
        beanFactory.registerSingleton("messageSource", messageSource);
        new SpringUtils().postProcessBeanFactory(beanFactory);
    }

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

    @BeforeEach
    void setUp() {
        controller = new TestInfoUserController();
        posOrderService = mock(IPosOrderService.class);
        infoUserService = mock(IInfoUserService.class);
        userWalletService = mock(IUserWalletService.class);
        merchantTokenSessionService = mock(MerchantTokenSessionService.class);
        ReflectionTestUtils.setField(controller, "posOrderService", posOrderService);
        ReflectionTestUtils.setField(controller, "infoUserService", infoUserService);
        ReflectionTestUtils.setField(controller, "userWalletService", userWalletService);
        ReflectionTestUtils.setField(controller, "merchantTokenSessionService", merchantTokenSessionService);
    }

    @Test
    void refusesDeletionWhenUserHasAnUnfinishedOrder() {
        when(posOrderService.exists(any(Wrapper.class))).thenReturn(true);

        ServiceException exception = assertThrows(ServiceException.class,
                () -> controller.deleuser(tokenFor(42L)));

        assertEquals("抱歉，您還有未完成的訂單，無法刪除帳號", exception.getMessage());
        verify(infoUserService, never()).deleteInfoUserByUserId(42L);
    }

    @Test
    void checksOnlyLatestUnfinishedStatesForEveryOrderRole() {
        when(posOrderService.exists(any(Wrapper.class))).thenReturn(false);
        when(infoUserService.deleteInfoUserByUserId(42L)).thenReturn(1);

        AjaxResult result = controller.deleuser(tokenFor(42L));

        assertEquals(HttpStatus.SUCCESS, result.get(AjaxResult.CODE_TAG));
        ArgumentCaptor<LambdaQueryWrapper<PosOrder>> captor =
                ArgumentCaptor.forClass(LambdaQueryWrapper.class);
        verify(posOrderService).exists(captor.capture());

        LambdaQueryWrapper<PosOrder> query = captor.getValue();
        String sql = query.getSqlSegment();
        Collection<Object> parameters = new ArrayList<>(query.getParamNameValuePairs().values());
        assertEquals(6, parameters.size());
        assertEquals(1, Collections.frequency(parameters, 0L));
        assertEquals(1, Collections.frequency(parameters, 1L));
        assertEquals(1, Collections.frequency(parameters, 2L));
        assertEquals(3, Collections.frequency(parameters, 42L));

        assertTrue(sql.contains("user_id"));
        assertTrue(sql.contains("qs_id"));
        assertTrue(sql.contains("sh_id"));
    }

    @Test
    void rejectsAuditRejectionWithoutReason() {
        InfoUser user = new InfoUser();
        user.setAuditStatus("2");
        user.setAuditRejectReason("　");

        AjaxResult result = controller.edit(user);

        assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
        verify(infoUserService, never()).updateInfoUser(any(InfoUser.class));
    }

    @Test
    void rejectsNewAuditRejectionWithoutReason() {
        InfoUser user = new InfoUser();
        user.setUserName("merchant");
        user.setPhone("0912345678");
        user.setAuditStatus("2");
        user.setAuditRejectReason("　");

        AjaxResult result = controller.add(user);

        assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
        verify(infoUserService, never()).insertInfoUser(any(InfoUser.class));
    }

    @Test
    void profileUpdateCannotChangeAuditStatus() {
        InfoUser request = new InfoUser();
        request.setAuditStatus("1");
        when(infoUserService.saveOrUpdate(any(InfoUser.class))).thenReturn(true);

        controller.setuser(tokenFor(42L), request);

        ArgumentCaptor<InfoUser> captor = ArgumentCaptor.forClass(InfoUser.class);
        verify(infoUserService).saveOrUpdate(captor.capture());
        assertNull(captor.getValue().getAuditStatus());
    }

    @Test
    void resubmittedRejectedProfileReturnsToPendingReview() {
        InfoUser request = new InfoUser();
        request.setUpdateUserInfo(true);
        when(infoUserService.saveOrUpdate(any(InfoUser.class))).thenReturn(true);

        controller.setuser(tokenFor(42L), request);

        ArgumentCaptor<LambdaUpdateWrapper> captor =
                ArgumentCaptor.forClass(LambdaUpdateWrapper.class);
        verify(infoUserService).update(captor.capture());
        LambdaUpdateWrapper<?> update = captor.getValue();
        Collection<Object> parameters = update.getParamNameValuePairs().values();
        assertTrue(update.getSqlSegment().contains("user_id"));
        assertTrue(update.getSqlSegment().contains("audit_status"));
        assertTrue(update.getSqlSet().contains("audit_status"));
        assertTrue(update.getSqlSet().contains("audit_reject_reason"));
        assertTrue(parameters.contains(42L));
        assertTrue(parameters.contains("2"));
        assertTrue(parameters.contains("0"));
    }

    @Test
    void ordinaryProfileUpdateDoesNotChangeReviewState() {
        InfoUser request = new InfoUser();
        request.setUpdateUserInfo(false);
        when(infoUserService.saveOrUpdate(any(InfoUser.class))).thenReturn(true);

        controller.setuser(tokenFor(42L), request);

        verify(infoUserService, never()).update(any(Wrapper.class));
    }

    @Test
    void trimsAndSavesAuditRejectionReason() {
        InfoUser user = new InfoUser();
        user.setAuditStatus("2");
        user.setAuditRejectReason("  证件照片模糊  ");
        when(infoUserService.updateInfoUser(user)).thenReturn(1);

        controller.edit(user);

        assertEquals("证件照片模糊", user.getAuditRejectReason());
        verify(infoUserService).updateInfoUser(user);
    }

    @Test
    void clearsOldReasonWhenAuditPasses() {
        InfoUser user = new InfoUser();
        user.setAuditStatus("1");
        user.setAuditRejectReason("旧原因");
        when(infoUserService.updateInfoUser(user)).thenReturn(1);

        controller.edit(user);

        assertNull(user.getAuditRejectReason());
        verify(infoUserService).updateInfoUser(user);
    }

    @Test
    void platformGenericAddCannotCreateMerchantSubaccount() {
        InfoUser user = new InfoUser();
        user.setUserType("5");

        AjaxResult result = controller.add(user);

        assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
        verify(infoUserService, never()).insertInfoUser(any(InfoUser.class));
    }

    @Test
    void platformGenericEditCannotModifyMerchantSubaccount() {
        InfoUser existing = new InfoUser();
        existing.setUserId(55L);
        existing.setUserType("5");
        when(infoUserService.selectInfoUserByUserId(55L)).thenReturn(existing);
        InfoUser request = new InfoUser();
        request.setUserId(55L);

        AjaxResult result = controller.edit(request);

        assertEquals(HttpStatus.ERROR, result.get(AjaxResult.CODE_TAG));
        verify(infoUserService, never()).updateInfoUser(request);
    }

    @Test
    void merchantLogoutRevokesOnlyCurrentTokenSession() {
        try (MockedStatic<AuthContext> authContext = mockStatic(AuthContext.class)) {
            authContext.when(AuthContext::requireJti)
                    .thenReturn("qtw_tokens:sh:app:936:current-session");

            controller.merchantLogout("merchant-token");
        }

        verify(merchantTokenSessionService)
                .logoutCurrent("qtw_tokens:sh:app:936:current-session");
    }

    private String tokenFor(Long userId) {
        return JwtUtil.setToken(String.valueOf(userId), "test-user");
    }

    private static class TestInfoUserController extends InfoUserController {
        @Override
        protected AjaxResult toAjax(int rows) {
            return new AjaxResult(HttpStatus.SUCCESS, "ok");
        }
    }
}
