package com.walkguide.service;

import com.walkguide.dto.response.AgoraTokenResponse;
import com.walkguide.entity.User;
import com.walkguide.exception.ResourceNotFoundException;
import com.walkguide.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;

import java.time.Instant;
import java.util.Optional;

import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
@DisplayName("AgoraTokenService Unit Tests")
class AgoraTokenServiceTest {

    @Mock
    UserRepository userRepository;

    @InjectMocks
    AgoraTokenService agoraTokenService;

    private User caller;
    private User receiver;

    @BeforeEach
    void setUp() {
        caller = User.builder()
                .id(3L)
                .email("caller@test.com")
                .displayName("Caller User")
                .role("ROLE_USER")
                .build();

        receiver = User.builder()
                .id(7L)
                .email("receiver@test.com")
                .displayName("Receiver Guardian")
                .role("ROLE_GUARDIAN")
                .build();
    }

    // ===== generateToken - Mode Demo (tanpa App ID/Certificate) =====

    @Test
    @DisplayName("generateToken - mode demo (appId kosong): token harus string kosong")
    void generateToken_demoMode_shouldReturnEmptyToken() {
        // appId dan appCertificate kosong (default @Value)
        ReflectionTestUtils.setField(agoraTokenService, "agoraAppId", "");
        ReflectionTestUtils.setField(agoraTokenService, "agoraAppCertificate", "");

        when(userRepository.findById(3L)).thenReturn(Optional.of(caller));
        when(userRepository.findById(7L)).thenReturn(Optional.of(receiver));

        AgoraTokenResponse result = agoraTokenService.generateToken(3L, 7L);

        assertThat(result).isNotNull();
        assertThat(result.getToken()).isEmpty();
        assertThat(result.getChannelName()).isEqualTo("call_3_7");
        assertThat(result.getAppId()).isEmpty();
        assertThat(result.getCallerName()).isEqualTo("Caller User");
        assertThat(result.getUid()).isEqualTo((int) (3L % Integer.MAX_VALUE));
    }

    @Test
    @DisplayName("generateToken - channel name harus deterministik: call_{min}_{max}")
    void generateToken_channelName_shouldBeDeterministicWithMinMax() {
        ReflectionTestUtils.setField(agoraTokenService, "agoraAppId", "");
        ReflectionTestUtils.setField(agoraTokenService, "agoraAppCertificate", "");

        when(userRepository.findById(3L)).thenReturn(Optional.of(caller));
        when(userRepository.findById(7L)).thenReturn(Optional.of(receiver));

        AgoraTokenResponse result1 = agoraTokenService.generateToken(3L, 7L);

        // Caller dan receiver dibalik — channel name tetap sama
        when(userRepository.findById(7L)).thenReturn(Optional.of(receiver));
        when(userRepository.findById(3L)).thenReturn(Optional.of(caller));
        AgoraTokenResponse result2 = agoraTokenService.generateToken(7L, 3L);

        assertThat(result1.getChannelName()).isEqualTo("call_3_7");
        assertThat(result2.getChannelName()).isEqualTo("call_3_7");
    }

    @Test
    @DisplayName("generateToken - expiresAt harus ~1 jam dari sekarang")
    void generateToken_expiresAt_shouldBeOneHourFromNow() {
        ReflectionTestUtils.setField(agoraTokenService, "agoraAppId", "");
        ReflectionTestUtils.setField(agoraTokenService, "agoraAppCertificate", "");

        when(userRepository.findById(3L)).thenReturn(Optional.of(caller));
        when(userRepository.findById(7L)).thenReturn(Optional.of(receiver));

        long before = Instant.now().getEpochSecond();
        AgoraTokenResponse result = agoraTokenService.generateToken(3L, 7L);
        long after = Instant.now().getEpochSecond();

        // expiresAt harus antara now+3600 dan now+3601 (toleransi 1 detik)
        assertThat(result.getExpiresAt()).isBetween(before + 3600, after + 3601);
    }

    @Test
    @DisplayName("generateToken - mode produksi (appId+cert valid): token harus diawali '006'")
    void generateToken_productionMode_shouldReturnTokenStartingWith006() {
        ReflectionTestUtils.setField(agoraTokenService, "agoraAppId", "abcdef1234567890abcdef1234567890");
        ReflectionTestUtils.setField(agoraTokenService, "agoraAppCertificate", "aabbccddeeff00112233445566778899");

        when(userRepository.findById(3L)).thenReturn(Optional.of(caller));
        when(userRepository.findById(7L)).thenReturn(Optional.of(receiver));

        AgoraTokenResponse result = agoraTokenService.generateToken(3L, 7L);

        assertThat(result.getToken()).startsWith("006");
        assertThat(result.getAppId()).isEqualTo("abcdef1234567890abcdef1234567890");
    }

    @Test
    @DisplayName("generateToken - caller tidak ditemukan harus throw ResourceNotFoundException")
    void generateToken_callerNotFound_shouldThrow() {
        ReflectionTestUtils.setField(agoraTokenService, "agoraAppId", "");
        ReflectionTestUtils.setField(agoraTokenService, "agoraAppCertificate", "");

        when(userRepository.findById(99L)).thenReturn(Optional.empty());

        assertThatThrownBy(() -> agoraTokenService.generateToken(99L, 7L))
                .isInstanceOf(ResourceNotFoundException.class)
                .hasMessageContaining("Caller not found");
    }

    @Test
    @DisplayName("generateToken - receiver tidak ditemukan harus throw ResourceNotFoundException")
    void generateToken_receiverNotFound_shouldThrow() {
        ReflectionTestUtils.setField(agoraTokenService, "agoraAppId", "");
        ReflectionTestUtils.setField(agoraTokenService, "agoraAppCertificate", "");

        when(userRepository.findById(3L)).thenReturn(Optional.of(caller));
        when(userRepository.findById(99L)).thenReturn(Optional.empty());

        assertThatThrownBy(() -> agoraTokenService.generateToken(3L, 99L))
                .isInstanceOf(ResourceNotFoundException.class)
                .hasMessageContaining("Receiver not found");
    }

    @Test
    @DisplayName("generateToken - uid harus modulo Integer.MAX_VALUE dari callerId")
    void generateToken_uid_shouldBeCallerIdModMaxInt() {
        ReflectionTestUtils.setField(agoraTokenService, "agoraAppId", "");
        ReflectionTestUtils.setField(agoraTokenService, "agoraAppCertificate", "");

        when(userRepository.findById(3L)).thenReturn(Optional.of(caller));
        when(userRepository.findById(7L)).thenReturn(Optional.of(receiver));

        AgoraTokenResponse result = agoraTokenService.generateToken(3L, 7L);

        assertThat(result.getUid()).isEqualTo((int) (3L % Integer.MAX_VALUE));
    }
}