package com.pandoli365.bibimbap.controller.api;

import com.pandoli365.bibimbap.data.UserAuthIdentityData;
import com.pandoli365.bibimbap.data.UserData;
import com.pandoli365.bibimbap.mapper.UserAuthIdentitiesMapper;
import com.pandoli365.bibimbap.mapper.UsersMapper;
import com.pandoli365.bibimbap.security.CsrfTokens;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpSession;

import java.lang.reflect.Method;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class UserControllerCsrfTest {

    @Mock
    private UsersMapper usersMapper;

    @Mock
    private UserAuthIdentitiesMapper userAuthIdentitiesMapper;

    @Mock
    private com.pandoli365.bibimbap.mapper.UserPermissionsMapper userPermissionsMapper;

    @Test
    void signupRejectsMissingCsrfBeforeMapperAccess() {
        UserController controller = controller();
        MockHttpServletRequest request = jsonPost("/signup");

        ResponseEntity<?> response = controller.signup(
                "테스터",
                "tester@example.com",
                "password123",
                "password123",
                "true",
                request
        );

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
        assertThat(response.getBody()).isInstanceOf(UserController.AuthResult.class);
        verifyNoInteractions(usersMapper, userAuthIdentitiesMapper);
    }

    @Test
    void loginRejectsMissingCsrfBeforeMapperAccess() {
        UserController controller = controller();
        MockHttpServletRequest request = jsonPost("/login");

        ResponseEntity<?> response = controller.login(
                "tester@example.com",
                "password123",
                null,
                request
        );

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
        assertThat(response.getBody()).isInstanceOf(UserController.AuthResult.class);
        verifyNoInteractions(usersMapper, userAuthIdentitiesMapper);
    }

    @Test
    void signupAcceptsValidCsrfParameter() {
        UserController controller = controller();
        MockHttpServletRequest request = jsonPost("/signup");
        addCsrfParameter(request);
        when(userAuthIdentitiesMapper.getUserAuthIdentityByProvider("email", "tester@example.com"))
                .thenReturn(null);
        when(usersMapper.addUser(any(UserData.class))).thenAnswer(invocation -> {
            UserData user = invocation.getArgument(0);
            user.setId(10L);
            return 1;
        });
        when(userAuthIdentitiesMapper.addUserAuthIdentity(any(UserAuthIdentityData.class)))
                .thenReturn(1);

        ResponseEntity<?> response = controller.signup(
                "테스터",
                "Tester@Example.com",
                "password123",
                "password123",
                "true",
                request
        );

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        verify(userAuthIdentitiesMapper).getUserAuthIdentityByProvider("email", "tester@example.com");
        verify(usersMapper).addUser(any(UserData.class));
        verify(userAuthIdentitiesMapper).addUserAuthIdentity(any(UserAuthIdentityData.class));
    }

    @Test
    void loginAcceptsValidCsrfHeaderAndAuthenticates() throws Exception {
        UserController controller = controller();
        MockHttpServletRequest request = jsonPost("/login");
        addCsrfHeader(request);
        UserAuthIdentityData identity = new UserAuthIdentityData();
        identity.setId(20L);
        identity.setUserId(10L);
        identity.setProvider("email");
        identity.setProviderUserId("tester@example.com");
        identity.setEmail("tester@example.com");
        identity.setPasswordHash(passwordHash(controller, "password123"));
        UserData user = new UserData();
        user.setId(10L);
        user.setDisplayName("테스터");
        user.setCanonicalEmail("tester@example.com");
        user.setRole("USER");
        user.setStatus("ACTIVE");
        when(userAuthIdentitiesMapper.getUserAuthIdentityByProvider("email", "tester@example.com"))
                .thenReturn(identity);
        when(usersMapper.getUser(10L)).thenReturn(user);

        ResponseEntity<?> response = controller.login(
                "Tester@Example.com",
                "password123",
                null,
                request
        );

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(request.getSession().getAttribute("userId")).isEqualTo(10L);
        verify(usersMapper).updateUser(user);
        verify(userAuthIdentitiesMapper).updateUserAuthIdentity(identity);
    }

    @Test
    void loginAcceptsValidCsrfHeaderAndReachesCredentialLookup() {
        UserController controller = controller();
        MockHttpServletRequest request = jsonPost("/login");
        addCsrfHeader(request);
        when(userAuthIdentitiesMapper.getUserAuthIdentityByProvider("email", "tester@example.com"))
                .thenReturn(null);

        ResponseEntity<?> response = controller.login(
                "Tester@Example.com",
                "password123",
                null,
                request
        );

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
        verify(userAuthIdentitiesMapper).getUserAuthIdentityByProvider("email", "tester@example.com");
        verifyNoInteractions(usersMapper);
    }

    private UserController controller() {
        return new UserController(usersMapper, userAuthIdentitiesMapper, userPermissionsMapper);
    }

    private MockHttpServletRequest jsonPost(String path) {
        MockHttpServletRequest request = new MockHttpServletRequest("POST", path);
        request.addHeader(HttpHeaders.ACCEPT, "application/json");
        request.addHeader("X-Requested-With", "XMLHttpRequest");
        return request;
    }

    private void addCsrfHeader(MockHttpServletRequest request) {
        MockHttpSession session = new MockHttpSession();
        String token = CsrfTokens.getOrCreate(session);
        request.setSession(session);
        request.addHeader(CsrfTokens.HEADER_NAME, token);
    }

    private void addCsrfParameter(MockHttpServletRequest request) {
        MockHttpSession session = new MockHttpSession();
        String token = CsrfTokens.getOrCreate(session);
        request.setSession(session);
        request.addParameter("_csrf", token);
    }

    private String passwordHash(UserController controller, String password) throws Exception {
        Method method = UserController.class.getDeclaredMethod("hashPassword", String.class);
        method.setAccessible(true);
        return (String) method.invoke(controller, password);
    }
}
