package de.cannamanage.service;

import de.cannamanage.domain.entity.RevokedToken;
import de.cannamanage.service.repository.RevokedTokenRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.time.Instant;
import java.util.UUID;

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

@ExtendWith(MockitoExtension.class)
class TokenRevocationServiceTest {

    @Mock
    private RevokedTokenRepository revokedTokenRepository;

    @InjectMocks
    private TokenRevocationService service;

    private String testJti;
    private UUID testUserId;
    private UUID testTenantId;

    @BeforeEach
    void setUp() {
        testJti = UUID.randomUUID().toString();
        testUserId = UUID.randomUUID();
        testTenantId = UUID.randomUUID();
    }

    @Test
    void isRevoked_notRevoked_returnsFalse() {
        when(revokedTokenRepository.existsByJti(testJti)).thenReturn(false);

        assertThat(service.isRevoked(testJti)).isFalse();
    }

    @Test
    void isRevoked_revoked_returnsTrue() {
        when(revokedTokenRepository.existsByJti(testJti)).thenReturn(true);

        assertThat(service.isRevoked(testJti)).isTrue();
    }

    @Test
    void isRevoked_nullJti_returnsFalse() {
        assertThat(service.isRevoked(null)).isFalse();
    }

    @Test
    void isRevoked_blankJti_returnsFalse() {
        assertThat(service.isRevoked("  ")).isFalse();
    }

    @Test
    void isRevoked_usesCache_onSecondCall() {
        when(revokedTokenRepository.existsByJti(testJti)).thenReturn(true);

        // First call goes to DB
        assertThat(service.isRevoked(testJti)).isTrue();
        // Second call should use cache
        assertThat(service.isRevoked(testJti)).isTrue();

        // DB should only be called once (cache handles second call)
        verify(revokedTokenRepository, times(1)).existsByJti(testJti);
    }

    @Test
    void revokeToken_savesRevocation() {
        Instant expiresAt = Instant.now().plusSeconds(3600);
        when(revokedTokenRepository.existsByJti(testJti)).thenReturn(false);

        service.revokeToken(testJti, testUserId, testTenantId, expiresAt, "logout");

        ArgumentCaptor<RevokedToken> captor = ArgumentCaptor.forClass(RevokedToken.class);
        verify(revokedTokenRepository).save(captor.capture());

        RevokedToken saved = captor.getValue();
        assertThat(saved.getJti()).isEqualTo(testJti);
        assertThat(saved.getUserId()).isEqualTo(testUserId);
        assertThat(saved.getTenantId()).isEqualTo(testTenantId);
        assertThat(saved.getExpiresAt()).isEqualTo(expiresAt);
        assertThat(saved.getReason()).isEqualTo("logout");
        assertThat(saved.getRevokedAt()).isNotNull();
    }

    @Test
    void revokeToken_alreadyRevoked_doesNotSaveAgain() {
        when(revokedTokenRepository.existsByJti(testJti)).thenReturn(true);

        service.revokeToken(testJti, testUserId, testTenantId, Instant.now().plusSeconds(3600), "duplicate");

        verify(revokedTokenRepository, never()).save(any());
    }

    @Test
    void cleanupExpiredTokens_deletesExpired() {
        when(revokedTokenRepository.deleteExpiredTokens(any(Instant.class))).thenReturn(5);

        int deleted = service.cleanupExpiredTokens();

        assertThat(deleted).isEqualTo(5);
        verify(revokedTokenRepository).deleteExpiredTokens(any(Instant.class));
    }

    @Test
    void cleanupExpiredTokens_nothingToDelete_returnsZero() {
        when(revokedTokenRepository.deleteExpiredTokens(any(Instant.class))).thenReturn(0);

        int deleted = service.cleanupExpiredTokens();

        assertThat(deleted).isEqualTo(0);
    }
}
