package com.pandoli365.bibimbap.controller;

import com.pandoli365.bibimbap.data.JamData;
import com.pandoli365.bibimbap.jam.JamAwardService;
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 java.time.OffsetDateTime;
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.when;

/**
 * F6 시상 산정 관리자 컨트롤러 단위 테스트 (plain Mockito, MockMvc 미사용).
 *
 * <p>computeAwards 의 게이트 순서(인증 401 → 인가 403 → CSRF 403 → 잼존재 404 →
 * F6 산정 가능 게이트 422 → 산정 200)를 전수 검증한다. F6 게이트는
 * status="CLOSED" 또는 now>evalEndAt 인 경우에만 통과한다.
 *
 * <p>VP-5 매핑은 메서드 주석 참조.
 */
@ExtendWith(MockitoExtension.class)
class JamAwardAdminControllerTest {

    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 PermissionGate gate;

    @Mock
    private JamAwardService jamAwardService;

    /** VP-5: 미인증 → 401, 산정·잼조회 미수행. */
    @Test
    void computeReturns401WhenUnauthenticated() {
        JamAwardAdminController controller = controller();
        MockHttpSession session = managerSession(ACTOR_ID);
        MockHttpServletRequest request = csrfPost(session);
        when(gate.isAuthenticated(session)).thenReturn(false);

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

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
        assertThat(response.getBody()).containsEntry("status", HttpStatus.UNAUTHORIZED.value());
        verify(jamAwardService, never()).recompute(anyLong());
        verify(jamsMapper, never()).getById(anyLong());
    }

    /** VP-5: 미인가 → 403, 산정 미수행. */
    @Test
    void computeReturns403WhenLacksPermission() {
        JamAwardAdminController 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.computeAwards(JAM_ID, request, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
        assertThat(response.getBody()).containsEntry("status", HttpStatus.FORBIDDEN.value());
        verify(jamAwardService, never()).recompute(anyLong());
    }

    /** VP-5: 게이트 통과 후 CSRF 누락 → 403, 잼조회·산정 미수행. */
    @Test
    void computeRejectsMissingCsrf() {
        JamAwardAdminController 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.computeAwards(JAM_ID, request, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN);
        assertThat(response.getBody()).containsEntry("status", 403);
        verify(jamsMapper, never()).getById(anyLong());
        verify(jamAwardService, never()).recompute(anyLong());
    }

    /** VP-5: 잼 없음 → 404, 산정 미수행. */
    @Test
    void computeReturns404WhenJamMissing() {
        JamAwardAdminController 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.computeAwards(JAM_ID, request, session);

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.NOT_FOUND);
        verify(jamAwardService, never()).recompute(anyLong());
    }

    /** VP-5: CLOSED 아니고 평가기간 진행 중 → 422, 산정 미수행. */
    @Test
    void computeReturns422WhenNotClosedAndEvalOngoing() {
        JamAwardAdminController 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(evalOngoingJam());

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

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNPROCESSABLE_ENTITY);
        assertThat(response.getBody()).containsEntry("message", "시상 산정 가능 상태가 아닙니다.");
        verify(jamAwardService, never()).recompute(anyLong());
    }

    /** VP-5: CLOSED 잼 → F6 통과 → 200, jamId·awardCounts 노출, recompute 호출. */
    @Test
    void computeSucceedsWhenClosed() {
        JamAwardAdminController 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(closedJam());
        when(jamAwardService.recompute(JAM_ID)).thenReturn(Map.of(
                "JUDGE", 2,
                "USER_RATING", 2,
                "POPULAR", 2,
                "GRAND", 2
        ));

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

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).containsEntry("jamId", JAM_ID);
        assertThat(response.getBody().get("awardCounts")).isNotNull();
        verify(jamAwardService).recompute(JAM_ID);
    }

    /** VP-5: EVAL 상태지만 평가 종료(now>evalEndAt) → F6 통과 → 200, recompute 호출. */
    @Test
    void computeSucceedsWhenEvalEnded() {
        JamAwardAdminController 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(evalEndedJam());
        when(jamAwardService.recompute(JAM_ID)).thenReturn(Map.of(
                "JUDGE", 2,
                "USER_RATING", 2,
                "POPULAR", 2,
                "GRAND", 2
        ));

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

        assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(response.getBody()).containsEntry("jamId", JAM_ID);
        assertThat(response.getBody().get("awardCounts")).isNotNull();
        verify(jamAwardService).recompute(JAM_ID);
    }

    // ==== helpers ====

    private JamAwardAdminController controller() {
        return new JamAwardAdminController(jamsMapper, gate, jamAwardService);
    }

    private JamData closedJam() {
        JamData jam = new JamData();
        jam.setId(JAM_ID);
        jam.setStatus("CLOSED");
        return jam;
    }

    /** 평가 진행 중 — status EVAL + evalEndAt 미래(F6 게이트 422 검증용). */
    private JamData evalOngoingJam() {
        JamData jam = new JamData();
        jam.setId(JAM_ID);
        jam.setStatus("EVAL");
        jam.setEvalEndAt(OffsetDateTime.now().plusHours(1));
        return jam;
    }

    /** 평가 종료 — status EVAL 이지만 evalEndAt 과거(F6 게이트 통과 검증용). */
    private JamData evalEndedJam() {
        JamData jam = new JamData();
        jam.setId(JAM_ID);
        jam.setStatus("EVAL");
        jam.setEvalEndAt(OffsetDateTime.now().minusHours(1));
        return jam;
    }

    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;
    }
}
