package com.storycove.service;

import com.storycove.entity.RefreshToken;
import com.storycove.repository.RefreshTokenRepository;
import com.storycove.util.JwtUtil;
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 java.time.LocalDateTime;
import java.util.Optional;

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

@ExtendWith(MockitoExtension.class)
class RefreshTokenServiceTest {

    @Mock
    private RefreshTokenRepository refreshTokenRepository;

    @Mock
    private JwtUtil jwtUtil;

    @InjectMocks
    private RefreshTokenService refreshTokenService;

    @Test
    void testCreateRefreshToken() {
        // Arrange
        String libraryId = "library-123";
        String userAgent = "Mozilla/5.0";
        String ipAddress = "192.168.1.1";

        when(jwtUtil.getRefreshExpirationMs()).thenReturn(1209600000L); // 14 days
        when(jwtUtil.generateRefreshToken()).thenReturn("test-refresh-token-12345");

        RefreshToken savedToken = new RefreshToken("test-refresh-token-12345",
                LocalDateTime.now().plusDays(14), libraryId, userAgent, ipAddress);

        when(refreshTokenRepository.save(any(RefreshToken.class))).thenReturn(savedToken);

        // Act
        RefreshToken result = refreshTokenService.createRefreshToken(libraryId, userAgent, ipAddress);

        // Assert
        assertNotNull(result);
        assertEquals("test-refresh-token-12345", result.getToken());
        assertEquals(libraryId, result.getLibraryId());
        assertEquals(userAgent, result.getUserAgent());
        assertEquals(ipAddress, result.getIpAddress());

        verify(jwtUtil).generateRefreshToken();
        verify(refreshTokenRepository).save(any(RefreshToken.class));
    }

    @Test
    void testFindByToken() {
        // Arrange
        String tokenString = "test-token";
        RefreshToken token = new RefreshToken(tokenString,
                LocalDateTime.now().plusDays(14), "lib-1", "UA", "127.0.0.1");

        when(refreshTokenRepository.findByToken(tokenString)).thenReturn(Optional.of(token));

        // Act
        Optional<RefreshToken> result = refreshTokenService.findByToken(tokenString);

        // Assert
        assertTrue(result.isPresent());
        assertEquals(tokenString, result.get().getToken());

        verify(refreshTokenRepository).findByToken(tokenString);
    }

    @Test
    void testVerifyRefreshToken_Valid() {
        // Arrange
        String tokenString = "valid-token";
        RefreshToken token = new RefreshToken(tokenString,
                LocalDateTime.now().plusDays(14), "lib-1", "UA", "127.0.0.1");

        when(refreshTokenRepository.findByToken(tokenString)).thenReturn(Optional.of(token));

        // Act
        Optional<RefreshToken> result = refreshTokenService.verifyRefreshToken(tokenString);

        // Assert
        assertTrue(result.isPresent());
        assertTrue(result.get().isValid());
    }

    @Test
    void testVerifyRefreshToken_Expired() {
        // Arrange
        String tokenString = "expired-token";
        RefreshToken token = new RefreshToken(tokenString,
                LocalDateTime.now().minusDays(1), "lib-1", "UA", "127.0.0.1"); // Expired

        when(refreshTokenRepository.findByToken(tokenString)).thenReturn(Optional.of(token));

        // Act
        Optional<RefreshToken> result = refreshTokenService.verifyRefreshToken(tokenString);

        // Assert
        assertFalse(result.isPresent()); // Expired tokens should be filtered out
    }

    @Test
    void testVerifyRefreshToken_Revoked() {
        // Arrange
        String tokenString = "revoked-token";
        RefreshToken token = new RefreshToken(tokenString,
                LocalDateTime.now().plusDays(14), "lib-1", "UA", "127.0.0.1");
        token.setRevokedAt(LocalDateTime.now()); // Revoked

        when(refreshTokenRepository.findByToken(tokenString)).thenReturn(Optional.of(token));

        // Act
        Optional<RefreshToken> result = refreshTokenService.verifyRefreshToken(tokenString);

        // Assert
        assertFalse(result.isPresent()); // Revoked tokens should be filtered out
    }

    @Test
    void testRevokeToken() {
        // Arrange
        RefreshToken token = new RefreshToken("token",
                LocalDateTime.now().plusDays(14), "lib-1", "UA", "127.0.0.1");

        when(refreshTokenRepository.save(any(RefreshToken.class))).thenReturn(token);

        // Act
        refreshTokenService.revokeToken(token);

        // Assert
        assertNotNull(token.getRevokedAt());
        assertTrue(token.isRevoked());

        verify(refreshTokenRepository).save(token);
    }

    @Test
    void testRevokeAllByLibraryId() {
        // Arrange
        String libraryId = "library-123";

        // Act
        refreshTokenService.revokeAllByLibraryId(libraryId);

        // Assert
        verify(refreshTokenRepository).revokeAllByLibraryId(eq(libraryId), any(LocalDateTime.class));
    }

    @Test
    void testRevokeAll() {
        // Act
        refreshTokenService.revokeAll();

        // Assert
        verify(refreshTokenRepository).revokeAll(any(LocalDateTime.class));
    }

    @Test
    void testCleanupExpiredTokens() {
        // Act
        refreshTokenService.cleanupExpiredTokens();

        // Assert
        verify(refreshTokenRepository).deleteExpiredTokens(any(LocalDateTime.class));
    }
}
