package com.pandoli365.bibimbap.controller;

import com.pandoli365.bibimbap.badge.BadgeService;
import com.pandoli365.bibimbap.badge.BadgeService.GrantResult;
import com.pandoli365.bibimbap.badge.BadgeService.RevokeResult;
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.Map;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
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 BadgeManageControllerTest {

    private static final long ACTOR_ID = 7L;
    private static final long TARGET_ID = 42L;
    private static final String BADGE_KEY = "FIRST_POST";
    private static final String BADGE_MANAGE = PermissionKeys.BADGE_MANAGE.name();

    @Mock
    private BadgeService badgeService;

    @Mock
    private PermissionGate permissionGate;

    // ---- grant ----

    // AC-5: CSRF 누락이면 게이트/서비스 진입 전 403. 컨트롤러 순서상 CSRF 가 가장 먼저라 service 미호출.
    @Test
    void grantRejectsMissingCsrf() {
        BadgeManageController controller = controller();
        MockHttpSession session = grantSession();
        MockHttpServletRequest request = noCsrfPost(session);

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

        assertThat(response.getStatusCode().value()).isEqualTo(403);
        verifyNoInteractions(badgeService);
    }

    // AC-11: CSRF 유효하나 미인증이면 401.
    @Test
    void grantReturns401WhenUnauthenticated() {
        BadgeManageController controller = controller();
        MockHttpSession session = grantSession();
        MockHttpServletRequest request = csrfPost(session);
        when(permissionGate.isAuthenticated(session)).thenReturn(false);

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

        assertThat(response.getStatusCode().value()).isEqualTo(401);
        verify(badgeService, never()).grantManual(anyLong(), anyString(), anyLong());
    }

    // AC-4/AC-11: 인증되었으나 BADGE_MANAGE 미보유(SUBADMIN 무권한)면 403.
    @Test
    void grantReturns403WhenMissingBadgeManage() {
        BadgeManageController controller = controller();
        MockHttpSession session = grantSession();
        MockHttpServletRequest request = csrfPost(session);
        when(permissionGate.isAuthenticated(session)).thenReturn(true);
        when(permissionGate.has(session, BADGE_MANAGE)).thenReturn(false);

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

        assertThat(response.getStatusCode().value()).isEqualTo(403);
        verify(badgeService, never()).grantManual(anyLong(), anyString(), anyLong());
    }

    // ADMIN 통과(게이트가 has=true) → GRANTED → 200, awarded=true.
    @Test
    void grantSucceedsForAdmin() {
        BadgeManageController controller = controller();
        MockHttpSession session = grantSession();
        MockHttpServletRequest request = csrfPost(session);
        when(permissionGate.isAuthenticated(session)).thenReturn(true);
        when(permissionGate.has(session, BADGE_MANAGE)).thenReturn(true);
        when(badgeService.grantManual(TARGET_ID, BADGE_KEY, ACTOR_ID)).thenReturn(GrantResult.GRANTED);

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

        assertThat(response.getStatusCode().value()).isEqualTo(200);
        assertThat(response.getBody()).containsEntry("awarded", true);
        verify(badgeService).grantManual(TARGET_ID, BADGE_KEY, ACTOR_ID);
    }

    // BADGE_MANAGE 키 보유 SUBADMIN 통과(게이트 has=true 동형) → GRANTED → 200. 권한키 경로 명시.
    @Test
    void grantSucceedsForSubadminWithBadgeManageKey() {
        BadgeManageController controller = controller();
        MockHttpSession session = grantSession();
        MockHttpServletRequest request = csrfPost(session);
        when(permissionGate.isAuthenticated(session)).thenReturn(true);
        when(permissionGate.has(session, BADGE_MANAGE)).thenReturn(true);
        when(badgeService.grantManual(TARGET_ID, BADGE_KEY, ACTOR_ID)).thenReturn(GrantResult.GRANTED);

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

        assertThat(response.getStatusCode().value()).isEqualTo(200);
        assertThat(response.getBody()).containsEntry("awarded", true);
        verify(badgeService).grantManual(TARGET_ID, BADGE_KEY, ACTOR_ID);
    }

    // ALREADY_ACTIVE → 409.
    @Test
    void grantReturns409WhenAlreadyActive() {
        BadgeManageController controller = controller();
        MockHttpSession session = grantSession();
        MockHttpServletRequest request = csrfPost(session);
        when(permissionGate.isAuthenticated(session)).thenReturn(true);
        when(permissionGate.has(session, BADGE_MANAGE)).thenReturn(true);
        when(badgeService.grantManual(TARGET_ID, BADGE_KEY, ACTOR_ID)).thenReturn(GrantResult.ALREADY_ACTIVE);

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

        assertThat(response.getStatusCode().value()).isEqualTo(409);
    }

    // INVALID_BADGE → 404.
    @Test
    void grantReturns404WhenBadgeKeyInvalid() {
        BadgeManageController controller = controller();
        MockHttpSession session = grantSession();
        MockHttpServletRequest request = csrfPost(session);
        when(permissionGate.isAuthenticated(session)).thenReturn(true);
        when(permissionGate.has(session, BADGE_MANAGE)).thenReturn(true);
        when(badgeService.grantManual(TARGET_ID, BADGE_KEY, ACTOR_ID)).thenReturn(GrantResult.INVALID_BADGE);

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

        assertThat(response.getStatusCode().value()).isEqualTo(404);
    }

    // USER_NOT_FOUND → 404.
    @Test
    void grantReturns404WhenUserMissing() {
        BadgeManageController controller = controller();
        MockHttpSession session = grantSession();
        MockHttpServletRequest request = csrfPost(session);
        when(permissionGate.isAuthenticated(session)).thenReturn(true);
        when(permissionGate.has(session, BADGE_MANAGE)).thenReturn(true);
        when(badgeService.grantManual(TARGET_ID, BADGE_KEY, ACTOR_ID)).thenReturn(GrantResult.USER_NOT_FOUND);

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

        assertThat(response.getStatusCode().value()).isEqualTo(404);
    }

    // ---- revoke ----

    // AC-5: CSRF 누락 → 403, service 미호출.
    @Test
    void revokeRejectsMissingCsrf() {
        BadgeManageController controller = controller();
        MockHttpSession session = grantSession();
        MockHttpServletRequest request = noCsrfPost(session);

        ResponseEntity<Map<String, Object>> response =
                controller.revoke(TARGET_ID, BADGE_KEY, "스팸 정정", request, session);

        assertThat(response.getStatusCode().value()).isEqualTo(403);
        verifyNoInteractions(badgeService);
    }

    // AC-11: 미인증 → 401.
    @Test
    void revokeReturns401WhenUnauthenticated() {
        BadgeManageController controller = controller();
        MockHttpSession session = grantSession();
        MockHttpServletRequest request = csrfPost(session);
        when(permissionGate.isAuthenticated(session)).thenReturn(false);

        ResponseEntity<Map<String, Object>> response =
                controller.revoke(TARGET_ID, BADGE_KEY, "스팸 정정", request, session);

        assertThat(response.getStatusCode().value()).isEqualTo(401);
        verify(badgeService, never()).revokeManual(anyLong(), anyString(), anyString());
    }

    // AC-4/AC-11: BADGE_MANAGE 미보유 → 403.
    @Test
    void revokeReturns403WhenMissingBadgeManage() {
        BadgeManageController controller = controller();
        MockHttpSession session = grantSession();
        MockHttpServletRequest request = csrfPost(session);
        when(permissionGate.isAuthenticated(session)).thenReturn(true);
        when(permissionGate.has(session, BADGE_MANAGE)).thenReturn(false);

        ResponseEntity<Map<String, Object>> response =
                controller.revoke(TARGET_ID, BADGE_KEY, "스팸 정정", request, session);

        assertThat(response.getStatusCode().value()).isEqualTo(403);
        verify(badgeService, never()).revokeManual(anyLong(), anyString(), anyString());
    }

    // 게이트 통과했으나 reason 공백 → 400, service 미호출.
    @Test
    void revokeReturns400WhenReasonBlank() {
        BadgeManageController controller = controller();
        MockHttpSession session = grantSession();
        MockHttpServletRequest request = csrfPost(session);
        when(permissionGate.isAuthenticated(session)).thenReturn(true);
        when(permissionGate.has(session, BADGE_MANAGE)).thenReturn(true);

        ResponseEntity<Map<String, Object>> response =
                controller.revoke(TARGET_ID, BADGE_KEY, "   ", request, session);

        assertThat(response.getStatusCode().value()).isEqualTo(400);
        verify(badgeService, never()).revokeManual(anyLong(), anyString(), anyString());
    }

    // 정상 회수: reason 유효 + REVOKED → 200, revoked=true. 컨트롤러가 reason 을 trim 해 전달.
    @Test
    void revokeSucceeds() {
        BadgeManageController controller = controller();
        MockHttpSession session = grantSession();
        MockHttpServletRequest request = csrfPost(session);
        when(permissionGate.isAuthenticated(session)).thenReturn(true);
        when(permissionGate.has(session, BADGE_MANAGE)).thenReturn(true);
        when(badgeService.revokeManual(TARGET_ID, BADGE_KEY, "스팸 정정")).thenReturn(RevokeResult.REVOKED);

        ResponseEntity<Map<String, Object>> response =
                controller.revoke(TARGET_ID, BADGE_KEY, "  스팸 정정  ", request, session);

        assertThat(response.getStatusCode().value()).isEqualTo(200);
        assertThat(response.getBody()).containsEntry("revoked", true);
        verify(badgeService).revokeManual(eq(TARGET_ID), eq(BADGE_KEY), eq("스팸 정정"));
    }

    // NOT_ACTIVE → 404.
    @Test
    void revokeReturns404WhenNotActive() {
        BadgeManageController controller = controller();
        MockHttpSession session = grantSession();
        MockHttpServletRequest request = csrfPost(session);
        when(permissionGate.isAuthenticated(session)).thenReturn(true);
        when(permissionGate.has(session, BADGE_MANAGE)).thenReturn(true);
        when(badgeService.revokeManual(TARGET_ID, BADGE_KEY, "스팸 정정")).thenReturn(RevokeResult.NOT_ACTIVE);

        ResponseEntity<Map<String, Object>> response =
                controller.revoke(TARGET_ID, BADGE_KEY, "스팸 정정", request, session);

        assertThat(response.getStatusCode().value()).isEqualTo(404);
    }

    // ---- helpers ----

    private BadgeManageController controller() {
        return new BadgeManageController(badgeService, permissionGate);
    }

    // actorId 추출용 userId + CSRF 토큰을 세팅한 세션.
    private MockHttpSession grantSession() {
        MockHttpSession session = new MockHttpSession();
        session.setAttribute("userId", ACTOR_ID);
        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;
    }
}
