package com.saas.admin.service;

import com.saas.admin.entity.RefreshToken;
import com.saas.admin.entity.User;
import com.saas.admin.repository.RefreshTokenRepository;
import com.saas.shared.enums.UserType;
import com.saas.shared.exception.BusinessException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

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

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

@DisplayName("RefreshTokenService Unit Tests")
public class RefreshTokenServiceTest {

    @Mock
    private RefreshTokenRepository refreshTokenRepository;

    @InjectMocks
    private RefreshTokenService refreshTokenService;

    private User testUser;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
        
        testUser = new User();
        testUser.setId(1L);
        testUser.setEmail("test@example.com");
        testUser.setPassword("hashed_password");
        testUser.setUserType(UserType.TENANT_USER);
        testUser.setFirstName("John");
        testUser.setLastName("Doe");
    }

    @Test
    @DisplayName("Create refresh token should generate and persist token")
    void testCreateRefreshToken() {
        // Arrange
        when(refreshTokenRepository.save(any(RefreshToken.class)))
                .thenAnswer(invocation -> invocation.getArgument(0));

        // Act
        String token = refreshTokenService.createRefreshToken(testUser);

        // Assert
        assertNotNull(token);
        assertFalse(token.isEmpty());
        verify(refreshTokenRepository, times(1)).save(any(RefreshToken.class));
    }

    @Test
    @DisplayName("Validate valid refresh token should return token without exception")
    void testValidateValidRefreshToken() {
        // Arrange
        RefreshToken validToken = new RefreshToken();
        validToken.setToken("valid_token_abc123");
        validToken.setUserId(1L);
        validToken.setUserEmail("test@example.com");
        validToken.setRevoked(false);
        validToken.setExpiresAt(Instant.now().plus(10, ChronoUnit.DAYS));

        when(refreshTokenRepository.findByToken("valid_token_abc123"))
                .thenReturn(Optional.of(validToken));

        // Act
        RefreshToken result = refreshTokenService.validateRefreshToken("valid_token_abc123");

        // Assert
        assertNotNull(result);
        assertEquals("valid_token_abc123", result.getToken());
        assertEquals(1L, result.getUserId());
        assertFalse(result.isRevoked());
    }

    @Test
    @DisplayName("Validate non-existent refresh token should throw exception")
    void testValidateNonExistentRefreshToken() {
        // Arrange
        when(refreshTokenRepository.findByToken("non_existent"))
                .thenReturn(Optional.empty());

        // Act & Assert
        assertThrows(BusinessException.class, () -> {
            refreshTokenService.validateRefreshToken("non_existent");
        });
    }

    @Test
    @DisplayName("Validate revoked refresh token should throw exception")
    void testValidateRevokedRefreshToken() {
        // Arrange
        RefreshToken revokedToken = new RefreshToken();
        revokedToken.setToken("revoked_token");
        revokedToken.setUserId(1L);
        revokedToken.setUserEmail("test@example.com");
        revokedToken.setRevoked(true);
        revokedToken.setExpiresAt(Instant.now().plus(10, ChronoUnit.DAYS));

        when(refreshTokenRepository.findByToken("revoked_token"))
                .thenReturn(Optional.of(revokedToken));

        // Act & Assert
        assertThrows(BusinessException.class, () -> {
            refreshTokenService.validateRefreshToken("revoked_token");
        });
    }

    @Test
    @DisplayName("Validate expired refresh token should throw exception")
    void testValidateExpiredRefreshToken() {
        // Arrange
        RefreshToken expiredToken = new RefreshToken();
        expiredToken.setToken("expired_token");
        expiredToken.setUserId(1L);
        expiredToken.setUserEmail("test@example.com");
        expiredToken.setRevoked(false);
        expiredToken.setExpiresAt(Instant.now().minus(1, ChronoUnit.DAYS)); // expired

        when(refreshTokenRepository.findByToken("expired_token"))
                .thenReturn(Optional.of(expiredToken));

        // Act & Assert
        assertThrows(BusinessException.class, () -> {
            refreshTokenService.validateRefreshToken("expired_token");
        });
    }

    @Test
    @DisplayName("Revoke refresh token should mark as revoked")
    void testRevokeRefreshToken() {
        // Arrange
        RefreshToken tokenToRevoke = new RefreshToken();
        tokenToRevoke.setToken("token_to_revoke");
        tokenToRevoke.setUserId(1L);
        tokenToRevoke.setUserEmail("test@example.com");
        tokenToRevoke.setRevoked(false);

        when(refreshTokenRepository.findByToken("token_to_revoke"))
                .thenReturn(Optional.of(tokenToRevoke));
        when(refreshTokenRepository.save(any(RefreshToken.class)))
                .thenAnswer(invocation -> invocation.getArgument(0));

        // Act
        refreshTokenService.revokeRefreshToken("token_to_revoke");

        // Assert
        verify(refreshTokenRepository, times(1)).save(any(RefreshToken.class));
    }

    @Test
    @DisplayName("Revoke all tokens for user should delete all user tokens")
    void testRevokeAllForUser() {
        // Act
        refreshTokenService.revokeAllForUser(1L);

        // Assert
        verify(refreshTokenRepository, times(1)).deleteByUserId(1L);
    }
}
