package com.pandoli365.bibimbap.controller;

import com.pandoli365.bibimbap.data.JamData;
import com.pandoli365.bibimbap.data.JamJudgeData;
import com.pandoli365.bibimbap.data.UserData;
import com.pandoli365.bibimbap.mapper.JamJudgesMapper;
import com.pandoli365.bibimbap.mapper.JamsMapper;
import com.pandoli365.bibimbap.mapper.UsersMapper;
import com.pandoli365.bibimbap.security.CsrfTokens;
import com.pandoli365.bibimbap.security.PermissionGate;
import com.pandoli365.bibimbap.security.PermissionKeys;
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.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpSession;

import java.util.List;
import java.util.Map;

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

/**
 * W2-2 심사위원 지정/해제/조회 컨트롤러 테스트.
 * 컨트롤러는 게이트(PermissionGate)만 소비하므로 ADMIN / SUBADMIN+키 통과는
 * gate.isAuthenticated + gate.has(GAME_JAM_MANAGE) 반환값으로 표현한다(JamAdminControllerTest 동형).
 *
 * VP-1 (지정 게이트): 통과(ADMIN/SUBADMIN+키) / 무키 403 / 미인증 401.
 * VP-5 (CSRF): 지정/해제 CSRF 누락 → 403 + mapper 미호출.
 * VP-6 (멱등): 이미 지정 재지정 → 409.
 * + 404(잼/대상유저 없음), 누구나 지정(VP-4), 해제, 조회.
 */
@ExtendWith(MockitoExtension.class)
class JamJudgeAdminControllerTest {

    private static final String GAME_JAM_MANAGE = PermissionKeys.GAME_JAM_MANAGE.name();
    private static final long ACTOR_ID = 99L;
    private static final long JAM_ID = 42L;
    private static final long TARGET_ID = 7L;

    @Mock
    private JamsMapper jamsMapper;

    @Mock
    private UsersMapper usersMapper;

    @Mock
    private JamJudgesMapper jamJudgesMapper;

    @Mock
    private PermissionGate gate;

    // ---- VP-1: 지정 게이트 (미인증 401 / 무키 403) ----

    @Test
    void assignReturns401WhenUnauthenticated() {
        JamJudgeAdminController controller = controller();
        MockHttpSession session = managerSession(ACTOR_ID);
        MockHttpServletRequest request = csrfPost(session);
        when(gate.isAuthenticated(session)).thenReturn(false);

        ResponseEntity<Map<String, Object>> response =
                controller.assign(JAM_ID, TARGET_ID, request, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
        assertThat(response.getBody()).containsEntry("status", HttpStatus.UNAUTHORIZED.value());
        verify(jamJudgesMapper, never()).insert(anyLong(), anyLong(), anyLong());
        verifyNoInteractions(jamsMapper);
        verifyNoInteractions(usersMapper);
    }

    @Test
    void assignReturns403WhenLacksPermission() {
        JamJudgeAdminController controller = controller();
        MockHttpSession session = managerSession(ACTOR_ID);
        MockHttpServletRequest request = csrfPost(session);
        when(gate.isAuthenticated(session)).thenReturn(true);
        when(gate.has(session, GAME_JAM_MANAGE)).thenReturn(false);

        ResponseEntity<Map<String, Object>> response =
                controller.assign(JAM_ID, TARGET_ID, request, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
        assertThat(response.getBody()).containsEntry("status", HttpStatus.FORBIDDEN.value());
        verify(jamJudgesMapper, never()).insert(anyLong(), anyLong(), anyLong());
        verifyNoInteractions(jamsMapper);
        verifyNoInteractions(usersMapper);
    }

    // ---- VP-5: CSRF 누락 → 403, 게이트 통과 후 매퍼 접근 전 차단 ----

    @Test
    void assignRejectsMissingCsrfBeforeMapperAccess() {
        JamJudgeAdminController controller = controller();
        MockHttpSession session = managerSession(ACTOR_ID);
        MockHttpServletRequest request = noCsrfPost(session);
        when(gate.isAuthenticated(session)).thenReturn(true);
        when(gate.has(session, GAME_JAM_MANAGE)).thenReturn(true);

        ResponseEntity<Map<String, Object>> response =
                controller.assign(JAM_ID, TARGET_ID, request, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
        assertThat(response.getBody()).containsEntry("status", 403);
        verifyNoInteractions(jamsMapper);
        verifyNoInteractions(usersMapper);
        verifyNoInteractions(jamJudgesMapper);
    }

    // ---- 지정 404: 잼 없음 / 대상 유저 없음 ----

    @Test
    void assignReturns404WhenJamMissing() {
        JamJudgeAdminController controller = controller();
        MockHttpSession session = managerSession(ACTOR_ID);
        MockHttpServletRequest request = csrfPost(session);
        when(gate.isAuthenticated(session)).thenReturn(true);
        when(gate.has(session, GAME_JAM_MANAGE)).thenReturn(true);
        when(jamsMapper.getById(JAM_ID)).thenReturn(null);

        ResponseEntity<Map<String, Object>> response =
                controller.assign(JAM_ID, TARGET_ID, request, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
        verify(jamJudgesMapper, never()).insert(anyLong(), anyLong(), anyLong());
    }

    @Test
    void assignReturns404WhenTargetUserMissing() {
        JamJudgeAdminController controller = controller();
        MockHttpSession session = managerSession(ACTOR_ID);
        MockHttpServletRequest request = csrfPost(session);
        when(gate.isAuthenticated(session)).thenReturn(true);
        when(gate.has(session, GAME_JAM_MANAGE)).thenReturn(true);
        when(jamsMapper.getById(JAM_ID)).thenReturn(jam(JAM_ID));
        when(usersMapper.getUser(TARGET_ID)).thenReturn(null);

        ResponseEntity<Map<String, Object>> response =
                controller.assign(JAM_ID, TARGET_ID, request, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
        verify(jamJudgesMapper, never()).insert(anyLong(), anyLong(), anyLong());
    }

    // ---- VP-6: 이미 지정 재지정 → 409 (멱등) ----

    @Test
    void assignReturns409WhenAlreadyJudge() {
        JamJudgeAdminController controller = controller();
        MockHttpSession session = managerSession(ACTOR_ID);
        MockHttpServletRequest request = csrfPost(session);
        when(gate.isAuthenticated(session)).thenReturn(true);
        when(gate.has(session, GAME_JAM_MANAGE)).thenReturn(true);
        when(jamsMapper.getById(JAM_ID)).thenReturn(jam(JAM_ID));
        when(usersMapper.getUser(TARGET_ID)).thenReturn(user(TARGET_ID));
        when(jamJudgesMapper.exists(JAM_ID, TARGET_ID)).thenReturn(true);

        ResponseEntity<Map<String, Object>> response =
                controller.assign(JAM_ID, TARGET_ID, request, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CONFLICT);
        verify(jamJudgesMapper, never()).insert(anyLong(), anyLong(), anyLong());
    }

    // ---- VP-1/VP-4: 지정 성공 (누구나 지정 — 대상 전역 role 검사 없음) ----

    @Test
    void assignInsertsJudgeWhenAuthorized() {
        JamJudgeAdminController controller = controller();
        MockHttpSession session = managerSession(ACTOR_ID);
        MockHttpServletRequest request = csrfPost(session);
        when(gate.isAuthenticated(session)).thenReturn(true);
        when(gate.has(session, GAME_JAM_MANAGE)).thenReturn(true);
        when(jamsMapper.getById(JAM_ID)).thenReturn(jam(JAM_ID));
        when(usersMapper.getUser(TARGET_ID)).thenReturn(user(TARGET_ID));
        when(jamJudgesMapper.exists(JAM_ID, TARGET_ID)).thenReturn(false);
        when(jamJudgesMapper.insert(JAM_ID, TARGET_ID, ACTOR_ID)).thenReturn(1);

        ResponseEntity<Map<String, Object>> response =
                controller.assign(JAM_ID, TARGET_ID, request, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).containsEntry("status", HttpStatus.OK.value());
        assertThat(response.getBody()).containsEntry("jamId", JAM_ID);
        assertThat(response.getBody()).containsEntry("userId", TARGET_ID);
        verify(jamJudgesMapper).insert(JAM_ID, TARGET_ID, ACTOR_ID);
    }

    // ---- 해제 게이트 / CSRF / 404 / 성공 ----

    @Test
    void removeReturns403WhenLacksPermission() {
        JamJudgeAdminController controller = controller();
        MockHttpSession session = managerSession(ACTOR_ID);
        MockHttpServletRequest request = csrfPost(session);
        when(gate.isAuthenticated(session)).thenReturn(true);
        when(gate.has(session, GAME_JAM_MANAGE)).thenReturn(false);

        ResponseEntity<Map<String, Object>> response =
                controller.remove(JAM_ID, TARGET_ID, request, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
        verify(jamJudgesMapper, never()).delete(anyLong(), anyLong());
    }

    @Test
    void removeRejectsMissingCsrfBeforeMapperAccess() {
        JamJudgeAdminController controller = controller();
        MockHttpSession session = managerSession(ACTOR_ID);
        MockHttpServletRequest request = noCsrfPost(session);
        when(gate.isAuthenticated(session)).thenReturn(true);
        when(gate.has(session, GAME_JAM_MANAGE)).thenReturn(true);

        ResponseEntity<Map<String, Object>> response =
                controller.remove(JAM_ID, TARGET_ID, request, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
        assertThat(response.getBody()).containsEntry("status", 403);
        verifyNoInteractions(jamJudgesMapper);
    }

    @Test
    void removeReturns404WhenNotAssigned() {
        JamJudgeAdminController controller = controller();
        MockHttpSession session = managerSession(ACTOR_ID);
        MockHttpServletRequest request = csrfPost(session);
        when(gate.isAuthenticated(session)).thenReturn(true);
        when(gate.has(session, GAME_JAM_MANAGE)).thenReturn(true);
        when(jamJudgesMapper.delete(JAM_ID, TARGET_ID)).thenReturn(0);

        ResponseEntity<Map<String, Object>> response =
                controller.remove(JAM_ID, TARGET_ID, request, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
    }

    @Test
    void removeDeletesJudgeWhenAuthorized() {
        JamJudgeAdminController controller = controller();
        MockHttpSession session = managerSession(ACTOR_ID);
        MockHttpServletRequest request = csrfPost(session);
        when(gate.isAuthenticated(session)).thenReturn(true);
        when(gate.has(session, GAME_JAM_MANAGE)).thenReturn(true);
        when(jamJudgesMapper.delete(JAM_ID, TARGET_ID)).thenReturn(1);

        ResponseEntity<Map<String, Object>> response =
                controller.remove(JAM_ID, TARGET_ID, request, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).containsEntry("removed", true);
        assertThat(response.getBody()).containsEntry("jamId", JAM_ID);
        assertThat(response.getBody()).containsEntry("userId", TARGET_ID);
        verify(jamJudgesMapper).delete(JAM_ID, TARGET_ID);
    }

    // ---- 조회 게이트 / 성공 ----

    @Test
    void listReturns401WhenUnauthenticated() {
        JamJudgeAdminController controller = controller();
        MockHttpSession session = managerSession(ACTOR_ID);
        when(gate.isAuthenticated(session)).thenReturn(false);

        ResponseEntity<Map<String, Object>> response = controller.list(JAM_ID, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
        verify(jamJudgesMapper, never()).listByJam(anyLong());
    }

    @Test
    void listReturns403WhenLacksPermission() {
        JamJudgeAdminController controller = controller();
        MockHttpSession session = managerSession(ACTOR_ID);
        when(gate.isAuthenticated(session)).thenReturn(true);
        when(gate.has(session, GAME_JAM_MANAGE)).thenReturn(false);

        ResponseEntity<Map<String, Object>> response = controller.list(JAM_ID, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
        verify(jamJudgesMapper, never()).listByJam(anyLong());
    }

    @Test
    void listReturnsJudgesWhenAuthorized() {
        JamJudgeAdminController controller = controller();
        MockHttpSession session = managerSession(ACTOR_ID);
        when(gate.isAuthenticated(session)).thenReturn(true);
        when(gate.has(session, GAME_JAM_MANAGE)).thenReturn(true);
        when(jamJudgesMapper.listByJam(JAM_ID)).thenReturn(List.of(judge(TARGET_ID, "심사위원")));

        ResponseEntity<Map<String, Object>> response = controller.list(JAM_ID, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).containsEntry("status", HttpStatus.OK.value());
        assertThat(response.getBody().get("judges")).isInstanceOf(List.class);
        assertThat((List<?>) response.getBody().get("judges")).hasSize(1);
        verify(jamJudgesMapper).listByJam(JAM_ID);
    }

    // ---- helpers ----

    private JamJudgeAdminController controller() {
        return new JamJudgeAdminController(jamsMapper, usersMapper, jamJudgesMapper, gate);
    }

    private JamData jam(long id) {
        JamData data = new JamData();
        data.setId(id);
        data.setStatus("RECRUIT");
        data.setTitle("게임잼");
        return data;
    }

    private UserData user(long id) {
        UserData data = new UserData();
        data.setId(id);
        return data;
    }

    private JamJudgeData judge(long userId, String displayName) {
        JamJudgeData data = new JamJudgeData();
        data.setUserId(userId);
        data.setDisplayName(displayName);
        data.setAssignedBy(ACTOR_ID);
        return data;
    }

    private MockHttpSession managerSession(long actorId) {
        MockHttpSession session = new MockHttpSession();
        session.setAttribute("userId", actorId);
        CsrfTokens.getOrCreate(session);
        return session;
    }

    private MockHttpServletRequest csrfPost(MockHttpSession session) {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setSession(session);
        request.addHeader(CsrfTokens.HEADER_NAME, (String) session.getAttribute(CsrfTokens.SESSION_ATTRIBUTE));
        return request;
    }

    private MockHttpServletRequest noCsrfPost(MockHttpSession session) {
        MockHttpServletRequest request = new MockHttpServletRequest();
        request.setSession(session);
        return request;
    }
}
