package com.zioinfo.kintex.publicsite.subscription;

import com.zioinfo.kintex.common.error.ApiException;
import com.zioinfo.kintex.common.error.ErrorCode;
import com.zioinfo.kintex.publicsite.subscription.dto.SubscriptionDtos.SubscribeRequest;
import com.zioinfo.kintex.publicsite.subscription.dto.SubscriptionDtos.SubscribeResult;
import com.zioinfo.kintex.publicsite.subscription.dto.SubscriptionDtos.UnsubscribeResult;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.Map;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;

/**
 * F-C1 관심 행사 구독 서비스 단위테스트 — 검증 가드·멱등·rate·수신거부·PII 마스킹.
 */
class EventSubscriptionServiceTest {

    private EventSubscriptionMapper mapper;
    private EventSubscriptionService service;

    @BeforeEach
    void setUp() {
        mapper = mock(EventSubscriptionMapper.class);
        service = new EventSubscriptionService(mapper);
        when(mapper.eventExists(anyString())).thenReturn(1);
        when(mapper.isEnded(anyString())).thenReturn(0);
    }

    @Test
    void subscribe_requiresPrivacyConsent() {
        ApiException ex = assertThrows(ApiException.class,
                () -> service.subscribe("e1", new SubscribeRequest("a@b.com", false)));
        assertEquals(ErrorCode.VALIDATION, ex.getCode());
        verify(mapper, never()).insert(any(), any(), any(), any(), any());
    }

    @Test
    void subscribe_rejectsInvalidEmail() {
        assertThrows(ApiException.class,
                () -> service.subscribe("e1", new SubscribeRequest("not-an-email", true)));
    }

    @Test
    void subscribe_rejectsUnknownEvent() {
        when(mapper.eventExists("e1")).thenReturn(0);
        ApiException ex = assertThrows(ApiException.class,
                () -> service.subscribe("e1", new SubscribeRequest("a@b.com", true)));
        assertEquals(ErrorCode.NOT_FOUND, ex.getCode());
    }

    @Test
    void subscribe_rejectsEndedEvent() {
        when(mapper.isEnded("e1")).thenReturn(1);
        assertThrows(ApiException.class,
                () -> service.subscribe("e1", new SubscribeRequest("a@b.com", true)));
    }

    @Test
    void subscribe_new_insertsAndMasksEmail() {
        when(mapper.findByEventEmail("e1", "kim.gildong@example.com")).thenReturn(null);
        when(mapper.countRecentByEmail(anyString(), anyInt())).thenReturn(0);

        SubscribeResult r = service.subscribe("e1",
                new SubscribeRequest("kim.gildong@example.com", true));

        assertFalse(r.alreadySubscribed());
        assertEquals("active", r.status());
        assertEquals("kim***@example.com", r.emailMasked());   // 원문 미노출
        verify(mapper).insert(anyString(), eq("e1"), eq("kim.gildong@example.com"),
                eq("kim***@example.com"), anyString());
    }

    @Test
    void subscribe_existingActive_isIdempotent() {
        when(mapper.findByEventEmail("e1", "a@b.com"))
                .thenReturn(Map.of("id", "es-1", "status", "active"));

        SubscribeResult r = service.subscribe("e1", new SubscribeRequest("a@b.com", true));

        assertTrue(r.alreadySubscribed());
        verify(mapper, never()).insert(any(), any(), any(), any(), any());
    }

    @Test
    void subscribe_reactivatesUnsubscribed() {
        when(mapper.findByEventEmail("e1", "a@b.com"))
                .thenReturn(Map.of("id", "es-1", "status", "unsubscribed"));

        SubscribeResult r = service.subscribe("e1", new SubscribeRequest("a@b.com", true));

        assertFalse(r.alreadySubscribed());
        assertEquals("active", r.status());
        verify(mapper).reactivate("es-1");
    }

    @Test
    void subscribe_rateLimited() {
        when(mapper.findByEventEmail(anyString(), anyString())).thenReturn(null);
        when(mapper.countRecentByEmail(anyString(), anyInt())).thenReturn(1);
        ApiException ex = assertThrows(ApiException.class,
                () -> service.subscribe("e1", new SubscribeRequest("a@b.com", true)));
        assertEquals(ErrorCode.CONFLICT, ex.getCode());
    }

    @Test
    void unsubscribe_success() {
        when(mapper.unsubscribeByToken("t")).thenReturn(1);
        UnsubscribeResult r = service.unsubscribe("t");
        assertTrue(r.ok());
    }

    @Test
    void unsubscribe_idempotentWhenAlreadyDone() {
        when(mapper.unsubscribeByToken("t")).thenReturn(0);
        when(mapper.tokenExists("t")).thenReturn(1);
        assertTrue(service.unsubscribe("t").ok());
    }

    @Test
    void unsubscribe_failsOnUnknownToken() {
        when(mapper.unsubscribeByToken("t")).thenReturn(0);
        when(mapper.tokenExists("t")).thenReturn(0);
        assertFalse(service.unsubscribe("t").ok());
    }
}
