package com.ruoyi.web.service.front;

import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.generator.domain.front.FrontUser;
import com.ruoyi.generator.service.front.IFrontUserService;
import com.ruoyi.web.controller.front.FrontAuthController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;

import java.lang.reflect.Field;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class FrontAuthServiceTest
{
    private FrontAuthService frontAuthService;

    private FrontAuthController frontAuthController;

    @Mock
    private IFrontUserService frontUserService;

    @Mock
    private TokenService tokenService;

    @Before
    public void setUp() throws Exception
    {
        frontAuthService = new FrontAuthService();
        frontAuthController = new FrontAuthController();
        setField(frontAuthService, "frontUserService", frontUserService);
        setField(frontAuthService, "tokenService", tokenService);
        setField(frontAuthController, "frontAuthService", frontAuthService);
    }

    @Test
    public void loginCreatesFrontToken()
    {
        FrontUser user = new FrontUser();
        user.setUserId(100L);
        user.setUsername("demo");
        user.setNickname("Demo");
        user.setPassword(SecurityUtils.encryptPassword("admin123"));
        user.setStatus("0");
        user.setDelFlag("0");

        when(frontUserService.selectByUsername("demo")).thenReturn(user);
        when(tokenService.createToken(any(LoginUser.class))).thenAnswer(invocation -> {
            LoginUser loginUser = invocation.getArgument(0);
            assertEquals(Constants.LOGIN_TYPE_FRONT, loginUser.getLoginType());
            assertEquals(Long.valueOf(100L), loginUser.getUserId());
            return "front-token";
        });

        String token = frontAuthService.login("demo", "admin123");

        assertEquals("front-token", token);
        verify(frontUserService).updateLastLoginTime(100L);
    }

    @Test
    public void loginRejectsNullPasswordWithCredentialFailure()
    {
        try
        {
            frontAuthService.login("demo", null);
            fail("Expected ServiceException");
        }
        catch (ServiceException e)
        {
            assertEquals("用户名或密码错误", e.getMessage());
        }

        verify(tokenService, never()).createToken(any(LoginUser.class));
        verify(frontUserService, never()).updateLastLoginTime(anyLong());
    }

    @Test
    public void loginRejectsNullBodyWithCredentialFailure()
    {
        try
        {
            frontAuthController.login(null);
            fail("Expected ServiceException");
        }
        catch (ServiceException e)
        {
            assertEquals("用户名或密码错误", e.getMessage());
        }

        verify(frontUserService, never()).selectByUsername(any());
        verify(tokenService, never()).createToken(any(LoginUser.class));
        verify(frontUserService, never()).updateLastLoginTime(anyLong());
    }

    @Test
    public void loginRejectsNullUsernameWithCredentialFailure()
    {
        try
        {
            frontAuthService.login(null, "admin123");
            fail("Expected ServiceException");
        }
        catch (ServiceException e)
        {
            assertEquals("用户名或密码错误", e.getMessage());
        }

        verify(frontUserService, never()).selectByUsername(any());
        verify(tokenService, never()).createToken(any(LoginUser.class));
        verify(frontUserService, never()).updateLastLoginTime(anyLong());
    }

    @Test
    public void loginRejectsBadPasswordBeforeAccountStatus()
    {
        FrontUser user = new FrontUser();
        user.setUserId(100L);
        user.setUsername("demo");
        user.setNickname("Demo");
        user.setPassword(SecurityUtils.encryptPassword("admin123"));
        user.setStatus("1");
        user.setDelFlag("0");

        when(frontUserService.selectByUsername("demo")).thenReturn(user);

        try
        {
            frontAuthService.login("demo", "wrong");
            fail("Expected ServiceException");
        }
        catch (ServiceException e)
        {
            assertEquals("用户名或密码错误", e.getMessage());
        }

        verify(tokenService, never()).createToken(any(LoginUser.class));
        verify(frontUserService, never()).updateLastLoginTime(anyLong());
    }

    @Test
    public void loginRejectsBadPasswordBeforeDeletedStatus()
    {
        FrontUser user = new FrontUser();
        user.setUserId(100L);
        user.setUsername("demo");
        user.setNickname("Demo");
        user.setPassword(SecurityUtils.encryptPassword("admin123"));
        user.setStatus("0");
        user.setDelFlag("2");

        when(frontUserService.selectByUsername("demo")).thenReturn(user);

        try
        {
            frontAuthService.login("demo", "wrong");
            fail("Expected ServiceException");
        }
        catch (ServiceException e)
        {
            assertEquals("用户名或密码错误", e.getMessage());
        }

        verify(tokenService, never()).createToken(any(LoginUser.class));
        verify(frontUserService, never()).updateLastLoginTime(anyLong());
    }

    @Test
    public void profileClearsPasswordBeforeReturning()
    {
        FrontUser user = new FrontUser();
        user.setUserId(100L);
        user.setUsername("demo");
        user.setPassword("encoded-password");

        when(frontUserService.selectById(100L)).thenReturn(user);

        FrontUser profile = frontAuthService.profile(100L);

        assertEquals("demo", profile.getUsername());
        assertNull(profile.getPassword());
    }

    private void setField(Object target, String name, Object value) throws Exception
    {
        Field field = target.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(target, value);
    }
}
