package com.pandoli365.bibimbap.controller;

import com.pandoli365.bibimbap.data.JamData;
import com.pandoli365.bibimbap.jam.JamLifecycle;
import com.pandoli365.bibimbap.jam.JamStatus;
import com.pandoli365.bibimbap.mapper.JamStatusLogMapper;
import com.pandoli365.bibimbap.mapper.JamsMapper;
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 org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.Model;

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

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class JamAdminControllerTest {

    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 = 7L;

    @Mock
    private JamsMapper jamsMapper;

    @Mock
    private JamStatusLogMapper jamStatusLogMapper;

    @Mock
    private PermissionGate gate;

    @Mock
    private JamLifecycle jamLifecycle;

    // ---- VP-1: console 게이트 (미인증 → /login, 미인가 → /) ----

    @Test
    void consoleRedirectsToLoginWhenUnauthenticated() {
        JamAdminController controller = controller();
        MockHttpSession session = managerSession(ACTOR_ID);
        MockHttpServletRequest request = csrfPost(session);
        Model model = new ExtendedModelMap();
        when(gate.isAuthenticated(session)).thenReturn(false);

        String view = controller.console(model, request, session);

        assertThat(view).isEqualTo("redirect:/login");
        verify(jamsMapper, never()).listAllForAdmin();
    }

    @Test
    void consoleRedirectsToRootWhenLacksPermission() {
        JamAdminController controller = controller();
        MockHttpSession session = managerSession(ACTOR_ID);
        MockHttpServletRequest request = csrfPost(session);
        Model model = new ExtendedModelMap();
        when(gate.isAuthenticated(session)).thenReturn(true);
        when(gate.has(session, GAME_JAM_MANAGE)).thenReturn(false);

        String view = controller.console(model, request, session);

        assertThat(view).isEqualTo("redirect:/");
        verify(jamsMapper, never()).listAllForAdmin();
    }

    @Test
    void consoleRendersListWhenAuthorized() {
        JamAdminController controller = controller();
        MockHttpSession session = managerSession(ACTOR_ID);
        MockHttpServletRequest request = csrfPost(session);
        Model model = new ExtendedModelMap();
        when(gate.isAuthenticated(session)).thenReturn(true);
        when(gate.has(session, GAME_JAM_MANAGE)).thenReturn(true);
        when(jamsMapper.listAllForAdmin()).thenReturn(List.of(jam(JAM_ID, "RECRUIT")));

        String view = controller.console(model, request, session);

        assertThat(view).isEqualTo("admin-jam-list");
        assertThat(model.getAttribute("jams")).isNotNull();
        assertThat(model.getAttribute("csrfToken")).isNotNull();
        verify(jamsMapper).listAllForAdmin();
    }

    // ---- VP-1: create 게이트 401/403 ----

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

        ResponseEntity<Map<String, Object>> response =
                controller.create("게임잼", null, null, null, null, null, null, null, null, request, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
        assertThat(response.getBody()).containsEntry("status", HttpStatus.UNAUTHORIZED.value());
        verify(jamsMapper, never()).insertJam(any());
    }

    @Test
    void createReturns403WhenLacksPermission() {
        JamAdminController 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.create("게임잼", null, null, null, null, null, null, null, null, request, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
        assertThat(response.getBody()).containsEntry("status", HttpStatus.FORBIDDEN.value());
        verify(jamsMapper, never()).insertJam(any());
    }

    // ---- VP-5: create CSRF 실패 (게이트 통과 후 매퍼 접근 전 차단) ----

    @Test
    void createRejectsMissingCsrfBeforeMapperAccess() {
        JamAdminController 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.create("게임잼", null, null, null, null, null, null, null, null, request, session);

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

    // ---- VP-2: create 성공 + 감사 로그 (from=null, to=RECRUIT) ----

    @Test
    void createInsertsJamAndAuditsRecruitTransition() {
        JamAdminController 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);
        doAnswer(inv -> {
            inv.getArgument(0, JamData.class).setId(JAM_ID);
            return 1;
        }).when(jamsMapper).insertJam(any(JamData.class));

        ResponseEntity<Map<String, Object>> response =
                controller.create("새 게임잼", null, null, null, null, null, null, null, null, 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().get("slug")).isNotNull();
        verify(jamsMapper).insertJam(any(JamData.class));
        verify(jamStatusLogMapper).insert(eq(JAM_ID), isNull(), eq("RECRUIT"), eq(ACTOR_ID), eq("MANUAL"));
    }

    // ---- VP-2: changeStatus 허용 전이 + 감사 로그 (from=RECRUIT, to=DEV) ----

    @Test
    void changeStatusAppliesAllowedTransitionAndAudits() {
        JamAdminController 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, "RECRUIT"));
        when(jamLifecycle.isAllowed(JamStatus.RECRUIT, JamStatus.DEV)).thenReturn(true);
        when(jamLifecycle.isPeriodReady(eq(JamStatus.DEV), any(JamData.class))).thenReturn(true);

        ResponseEntity<Map<String, Object>> response =
                controller.changeStatus(JAM_ID, "DEV", 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("jamStatus", "DEV");
        verify(jamsMapper).updateStatus(eq(JAM_ID), eq("DEV"));
        verify(jamStatusLogMapper).insert(eq(JAM_ID), eq("RECRUIT"), eq("DEV"), eq(ACTOR_ID), eq("MANUAL"));
    }

    // ---- VP-2: changeStatus 거부 전이 (409, updateStatus 미호출) ----

    @Test
    void changeStatusReturns409WhenTransitionDisallowed() {
        JamAdminController 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, "RECRUIT"));
        when(jamLifecycle.isAllowed(JamStatus.RECRUIT, JamStatus.EVAL)).thenReturn(false);

        ResponseEntity<Map<String, Object>> response =
                controller.changeStatus(JAM_ID, "EVAL", request, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.CONFLICT);
        verify(jamsMapper, never()).updateStatus(anyLong(), anyString());
        verify(jamStatusLogMapper, never()).insert(anyLong(), any(), anyString(), any(), anyString());
    }

    // ---- changeStatus 잼 없음 (404) ----

    @Test
    void changeStatusReturns404WhenJamMissing() {
        JamAdminController 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.changeStatus(JAM_ID, "DEV", request, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
        verify(jamsMapper, never()).updateStatus(anyLong(), anyString());
    }

    // ---- helpers ----

    private JamAdminController controller() {
        return new JamAdminController(jamsMapper, jamStatusLogMapper, gate, jamLifecycle);
    }

    private JamData jam(long id, String status) {
        JamData data = new JamData();
        data.setId(id);
        data.setStatus(status);
        data.setTitle("게임잼");
        data.setIsVisible(true);
        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;
    }
}
